Merge "Handle legacy broadcasts from dumpstate logic in onFinished() callback"
diff --git a/Android.bp b/Android.bp
index ee3cb8f..6fc233c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -163,7 +163,7 @@
 }
 
 filegroup {
-    name: "framework-srcs",
+    name: "framework-non-updatable-sources",
     srcs: [
         // Java/AIDL sources under frameworks/base
         ":framework-core-sources",
@@ -211,6 +211,14 @@
     ],
 }
 
+filegroup {
+    name: "framework-all-sources",
+    srcs: [
+        ":framework-non-updatable-sources",
+        ":updatable-media-srcs",
+    ],
+}
+
 java_defaults {
     name: "framework-aidl-export-defaults",
     aidl: {
@@ -236,8 +244,9 @@
     },
 
     required: [
-        // TODO: remove gps_debug when the build system propagates "required" properly.
+        // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
         "gps_debug.conf",
+	"protolog.conf.json.gz",
     ],
 }
 
@@ -288,15 +297,12 @@
     defaults: ["framework-aidl-export-defaults"],
     installable: true,
 
-    srcs: [
-        ":framework-srcs",
-        "core/java/**/*.logtags",
-    ],
-
     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",
@@ -359,6 +365,7 @@
 java_library {
     name: "framework-minus-apex",
     defaults: ["framework-defaults"],
+    srcs: [":framework-non-updatable-sources"],
     javac_shard_size: 150,
 }
 
@@ -379,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",
@@ -502,7 +517,6 @@
     sdk_version: "core_platform",
     static_libs: [
         "libphonenumber-platform",
-        "nist-sip",
         "tagsoup",
         "rappor",
         "libtextclassifier-java",
@@ -884,10 +898,23 @@
     // 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-srcs",
+        ":framework-non-updatable-sources",
         "core/java/**/*.logtags",
         "test-base/src/**/*.java",
         ":opt-telephony-srcs",
@@ -907,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",
@@ -952,7 +979,7 @@
 stubs_defaults {
     name: "metalava-api-stubs-default",
     srcs: [
-        ":framework-srcs",
+        ":framework-non-updatable-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -964,7 +991,7 @@
     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",
@@ -975,6 +1002,7 @@
         "api-versions-jars-dir",
     ],
     sdk_version: "core_platform",
+    filter_packages: packages_to_document,
 }
 
 droidstubs {
@@ -1299,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",
@@ -1473,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",
 }
 
@@ -1486,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/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml
index b2a9524..893c8ca 100644
--- a/apct-tests/perftests/multiuser/AndroidManifest.xml
+++ b/apct-tests/perftests/multiuser/AndroidManifest.xml
@@ -22,6 +22,7 @@
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 32107b4..e74e4a9 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -38,8 +38,10 @@
 import android.os.IBinder;
 import android.os.IProgressListener;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.perftests.utils.ShellHelper;
 import android.util.Log;
 import android.view.WindowManagerGlobal;
 
@@ -85,6 +87,14 @@
 
     private static final String DUMMY_PACKAGE_NAME = "perftests.multiuser.apps.dummyapp";
 
+    // Copy of UserSystemPackageInstaller whitelist mode constants.
+    private static final String PACKAGE_WHITELIST_MODE_PROP =
+            "persist.debug.user.package_whitelist_mode";
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+    private static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
     private UserManager mUm;
     private ActivityManager mAm;
     private IActivityManager mIam;
@@ -442,6 +452,55 @@
         }
     }
 
+    // TODO: This is just a POC. Do this properly and add more.
+    /** Tests starting (unlocking) a newly-created profile using the user-type-pkg-whitelist. */
+    @Test
+    public void managedProfileUnlock_usingWhitelist() throws Exception {
+        assumeTrue(mHasManagedUserFeature);
+        final int origMode = getUserTypePackageWhitelistMode();
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE
+                | USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+
+        try {
+            while (mRunner.keepRunning()) {
+                mRunner.pauseTiming();
+                final int userId = createManagedProfile();
+                mRunner.resumeTiming();
+
+                startUserInBackground(userId);
+
+                mRunner.pauseTiming();
+                removeUser(userId);
+                mRunner.resumeTiming();
+            }
+        } finally {
+            setUserTypePackageWhitelistMode(origMode);
+        }
+    }
+    /** Tests starting (unlocking) a newly-created profile NOT using the user-type-pkg-whitelist. */
+    @Test
+    public void managedProfileUnlock_notUsingWhitelist() throws Exception {
+        assumeTrue(mHasManagedUserFeature);
+        final int origMode = getUserTypePackageWhitelistMode();
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+
+        try {
+            while (mRunner.keepRunning()) {
+                mRunner.pauseTiming();
+                final int userId = createManagedProfile();
+                mRunner.resumeTiming();
+
+                startUserInBackground(userId);
+
+                mRunner.pauseTiming();
+                removeUser(userId);
+                mRunner.resumeTiming();
+            }
+        } finally {
+            setUserTypePackageWhitelistMode(origMode);
+        }
+    }
+
     /** Creates a new user, returning its userId. */
     private int createUserNoFlags() {
         return createUserWithFlags(/* flags= */ 0);
@@ -458,6 +517,10 @@
     private int createManagedProfile() {
         final UserInfo userInfo = mUm.createProfileForUser("TestProfile",
                 UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
+        if (userInfo == null) {
+            throw new IllegalStateException("Creating managed profile failed. Most likely there is "
+                    + "already a pre-existing profile on the device.");
+        }
         mUsersToRemove.add(userInfo.id);
         return userInfo.id;
     }
@@ -627,6 +690,20 @@
         }
     }
 
+    /** Gets the PACKAGE_WHITELIST_MODE_PROP System Property. */
+    private int getUserTypePackageWhitelistMode() {
+        return SystemProperties.getInt(PACKAGE_WHITELIST_MODE_PROP,
+                USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+    }
+
+    /** Sets the PACKAGE_WHITELIST_MODE_PROP System Property to the given value. */
+    private void setUserTypePackageWhitelistMode(int mode) {
+        String result = ShellHelper.runShellCommand(
+                String.format("setprop %s %d", PACKAGE_WHITELIST_MODE_PROP, mode));
+        attestFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+                result != null && result.contains("Failed"));
+    }
+
     private void removeUser(int userId) {
         try {
             mUm.removeUser(userId);
diff --git a/apct-tests/perftests/textclassifier/run.sh b/apct-tests/perftests/textclassifier/run.sh
index 8660d26..d36d190 100755
--- a/apct-tests/perftests/textclassifier/run.sh
+++ b/apct-tests/perftests/textclassifier/run.sh
@@ -1,5 +1,5 @@
 set -e
-make TextClassifierPerfTests perf-setup.sh
+build/soong/soong_ui.bash --make-mode TextClassifierPerfTests perf-setup.sh
 adb install ${OUT}/testcases/TextClassifierPerfTests/arm64/TextClassifierPerfTests.apk
 adb shell cmd package compile -m speed -f com.android.perftests.textclassifier
 adb push ${OUT}/obj/EXECUTABLES/perf-setup.sh_intermediates/perf-setup.sh /data/local/tmp/
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/api/current.txt b/api/current.txt
index e3fde23..fc7685d 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(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureInfo> CREATOR;
+    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();
@@ -6799,6 +6800,7 @@
     method public boolean isResetPasswordTokenActive(android.content.ComponentName);
     method public boolean isSecurityLoggingEnabled(@Nullable android.content.ComponentName);
     method public boolean isUninstallBlocked(@Nullable android.content.ComponentName, String);
+    method public boolean isUniqueDeviceAttestationSupported();
     method public boolean isUsingUnifiedPassword(@NonNull android.content.ComponentName);
     method public void lockNow();
     method public void lockNow(int);
@@ -6980,6 +6982,7 @@
     field public static final int FLAG_PARENT_CAN_ACCESS_MANAGED = 1; // 0x1
     field public static final int ID_TYPE_BASE_INFO = 1; // 0x1
     field public static final int ID_TYPE_IMEI = 4; // 0x4
+    field public static final int ID_TYPE_INDIVIDUAL_ATTESTATION = 16; // 0x10
     field public static final int ID_TYPE_MEID = 8; // 0x8
     field public static final int ID_TYPE_SERIAL = 2; // 0x2
     field public static final int INSTALLKEY_REQUEST_CREDENTIALS_ACCESS = 1; // 0x1
@@ -7216,18 +7219,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();
@@ -7237,12 +7240,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();
@@ -7878,6 +7881,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();
@@ -23088,8 +23092,9 @@
 
   public class LocationManager {
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addGpsStatusListener(android.location.GpsStatus.Listener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull android.location.OnNmeaMessageListener, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean addNmeaListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.OnNmeaMessageListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void addProximityAlert(double, double, float, long, @NonNull android.app.PendingIntent);
     method public void addTestProvider(@NonNull String, boolean, boolean, boolean, boolean, boolean, boolean, boolean, int, int);
     method @Deprecated public void clearTestProviderEnabled(@NonNull String);
@@ -23106,12 +23111,15 @@
     method @NonNull public java.util.List<java.lang.String> getProviders(@NonNull android.location.Criteria, boolean);
     method public boolean isLocationEnabled();
     method public boolean isProviderEnabled(@NonNull String);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull android.location.GnssMeasurementsEvent.Callback, @Nullable android.os.Handler);
-    method public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssMeasurementsEvent.Callback);
+    method @Deprecated public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull android.location.GnssNavigationMessage.Callback, @Nullable android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssNavigationMessageCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssNavigationMessage.Callback);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull android.location.GnssStatus.Callback, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public boolean registerGnssStatusCallback(@NonNull java.util.concurrent.Executor, @NonNull android.location.GnssStatus.Callback);
     method @Deprecated public void removeGpsStatusListener(android.location.GpsStatus.Listener);
     method public void removeNmeaListener(@NonNull android.location.OnNmeaMessageListener);
     method @RequiresPermission(anyOf={"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"}, apis="..22") public void removeProximityAlert(@NonNull android.app.PendingIntent);
@@ -23120,7 +23128,9 @@
     method public void removeUpdates(@NonNull android.app.PendingIntent);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull String, long, float, @NonNull android.app.PendingIntent);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(long, float, @NonNull android.location.Criteria, @NonNull android.app.PendingIntent);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestSingleUpdate(@NonNull String, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
@@ -23967,6 +23977,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);
@@ -29461,7 +29472,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();
@@ -45010,6 +45022,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
@@ -50448,6 +50461,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();
@@ -50792,6 +50806,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);
@@ -52146,6 +52161,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
@@ -52263,6 +52279,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();
@@ -52354,6 +52371,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);
@@ -52666,7 +52684,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();
@@ -52690,7 +52708,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);
@@ -53034,6 +53052,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/system-current.txt b/api/system-current.txt
index 2843916..279d2c8 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";
@@ -1381,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";
@@ -1662,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
@@ -1730,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 {
@@ -3429,6 +3432,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public boolean isProviderPackage(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean registerGnssBatchedLocationCallback(long, boolean, @NonNull android.location.BatchedLocationCallback, @Nullable android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String);
     method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean);
@@ -4720,7 +4724,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>);
@@ -5515,7 +5519,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();
@@ -5679,6 +5683,14 @@
 
 }
 
+package android.os.telephony {
+
+  public class TelephonyRegistryManager {
+    method public void notifyCarrierNetworkChange(boolean);
+  }
+
+}
+
 package android.permission {
 
   public final class PermissionControllerManager {
@@ -5707,6 +5719,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";
   }
 
@@ -6168,6 +6181,7 @@
     field public static final int ID_TYPE_IMEI = 2; // 0x2
     field public static final int ID_TYPE_MEID = 3; // 0x3
     field public static final int ID_TYPE_SERIAL = 1; // 0x1
+    field public static final int USE_INDIVIDUAL_ATTESTATION = 4; // 0x4
   }
 
   public class DeviceIdAttestationException extends java.lang.Exception {
@@ -7234,6 +7248,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);
@@ -7726,6 +7747,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();
@@ -7827,6 +7866,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
@@ -8174,7 +8215,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);
@@ -8195,6 +8236,7 @@
     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();
@@ -8213,6 +8255,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();
@@ -8220,7 +8263,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();
@@ -8644,6 +8687,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();
@@ -8661,6 +8705,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);
@@ -8711,6 +8756,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 {
@@ -9485,14 +9533,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 6e28f67..75d80bd 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);
   }
 
 }
@@ -739,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
@@ -920,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 {
@@ -1085,6 +1094,7 @@
     method @NonNull public String[] getIgnoreSettingsWhitelist();
     method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@NonNull android.location.LocationRequest, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle);
   }
@@ -2426,6 +2436,7 @@
     field public static final int ID_TYPE_IMEI = 2; // 0x2
     field public static final int ID_TYPE_MEID = 3; // 0x3
     field public static final int ID_TYPE_SERIAL = 1; // 0x1
+    field public static final int USE_INDIVIDUAL_ATTESTATION = 4; // 0x4
   }
 
   public class DeviceIdAttestationException extends java.lang.Exception {
@@ -2903,6 +2914,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 +3347,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/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/statsd/Android.bp b/cmds/statsd/Android.bp
index c9a4b3b..c79b0ca 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,59 @@
         "src/anomaly/subscriber_util.cpp",
         "src/condition/CombinationConditionTracker.cpp",
         "src/condition/condition_util.cpp",
-        "src/condition/SimpleConditionTracker.cpp",
         "src/condition/ConditionWizard.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/SurfaceflingerStatsPuller.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 +123,24 @@
     ],
 
     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",
+        "libtimestats_proto",
+        "libutils",
     ],
 }
 
@@ -210,54 +214,56 @@
 
         "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/ConfigManager_test.cpp",
-        "tests/external/puller_util_test.cpp",
-        "tests/external/GpuStatsPuller_test.cpp",
-        "tests/external/IncidentReportArgs_test.cpp",
-        "tests/external/StatsPuller_test.cpp",
-        "tests/indexed_priority_queue_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/ConditionTimer_test.cpp",
         "tests/condition/SimpleConditionTracker_test.cpp",
         "tests/condition/StateConditionTracker_test.cpp",
-        "tests/condition/ConditionTimer_test.cpp",
-        "tests/metrics/OringDurationTracker_test.cpp",
-        "tests/metrics/MaxDurationTracker_test.cpp",
+        "tests/ConfigManager_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/external/SurfaceflingerStatsPuller_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/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: [
@@ -287,17 +293,17 @@
         // 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: {
@@ -337,11 +343,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/atoms.proto b/cmds/statsd/src/atoms.proto
index ae751ce..b71a86b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -335,10 +335,12 @@
         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: 10064
+    // Next: 10065
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -404,6 +406,7 @@
         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.
@@ -2618,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;
@@ -3065,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;
 
@@ -3075,7 +3080,7 @@
         ENTERED = 1;
         EXITED = 2;
     }
-    optional State state = 4;
+    optional State state = 4 [(state_field_option).option = EXCLUSIVE];
 }
 
 /*
@@ -3968,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];
 }
 
 /**
@@ -4094,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;
 }
 
 /*
@@ -7166,7 +7215,6 @@
     repeated int64 frame_counts = 2;
 }
 
-
 /**
  * Information about camera facing and API level usage.
  * Logged from:
@@ -7191,3 +7239,39 @@
     }
     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/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 475f18a..7a183a3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -17,12 +17,17 @@
 #define DEBUG false
 #include "Log.h"
 
+#include "StatsPullerManager.h"
+
 #include <android/os/IStatsCompanionService.h>
 #include <android/os/IStatsPullerCallback.h>
 #include <cutils/log.h>
 #include <math.h>
 #include <stdint.h>
+
 #include <algorithm>
+#include <iostream>
+
 #include "../StatsService.h"
 #include "../logd/LogEvent.h"
 #include "../stats_log_util.h"
@@ -32,13 +37,11 @@
 #include "ResourceHealthManagerPuller.h"
 #include "StatsCallbackPuller.h"
 #include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
 #include "SubsystemSleepStatePuller.h"
+#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
-#include <iostream>
-
 using std::make_shared;
 using std::map;
 using std::shared_ptr;
@@ -153,6 +156,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)}},
@@ -266,6 +272,10 @@
         // App ops
         {android::util::APP_OPS,
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+        // SurfaceflingerStatsGlobalInfo
+        {android::util::SURFACEFLINGER_STATS_GLOBAL_INFO,
+         {.puller =
+                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
 };
 
 StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
new file mode 100644
index 0000000..23b2236
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#include "SurfaceflingerStatsPuller.h"
+
+#include <cutils/compiler.h>
+
+#include <numeric>
+
+#include "logd/LogEvent.h"
+#include "stats_log_util.h"
+#include "statslog.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
+}
+
+bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
+    switch (mTagId) {
+        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
+            return pullGlobalInfo(data);
+        default:
+            break;
+    }
+
+    return false;
+}
+
+static int64_t getTotalTime(
+        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
+                buckets) {
+    int64_t total = 0;
+    for (const auto& bucket : buckets) {
+        if (bucket.time_millis() == 1000) {
+            continue;
+        }
+
+        total += bucket.time_millis() * bucket.frame_count();
+    }
+
+    return total;
+}
+
+bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
+    std::string protoBytes;
+    if (CC_UNLIKELY(mStatsProvider)) {
+        protoBytes = mStatsProvider();
+    } else {
+        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
+        if (!pipe.get()) {
+            return false;
+        }
+        char buf[1024];
+        size_t bytesRead = 0;
+        do {
+            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
+            protoBytes.append(buf, bytesRead);
+        } while (bytesRead > 0);
+    }
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.ParseFromString(protoBytes);
+
+    int64_t totalTime = getTotalTime(proto.present_to_present());
+
+    data->clear();
+    data->reserve(1);
+    std::shared_ptr<LogEvent> event =
+            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
+                                  getElapsedRealtimeNs());
+    if (!event->write(proto.total_frames())) return false;
+    if (!event->write(proto.missed_frames())) return false;
+    if (!event->write(proto.client_composition_frames())) return false;
+    if (!event->write(proto.display_on_time())) return false;
+    if (!event->write(totalTime)) return false;
+    event->init();
+    data->emplace_back(event);
+
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
new file mode 100644
index 0000000..ed7153e
--- /dev/null
+++ b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <timestatsproto/TimeStatsProtoHeader.h>
+
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Pull metrics from Surfaceflinger
+ */
+class SurfaceflingerStatsPuller : public StatsPuller {
+public:
+    explicit SurfaceflingerStatsPuller(const int tagId);
+
+    // StatsPuller interface
+    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
+
+protected:
+    // Test-only, for injecting fake data
+    using StatsProvider = std::function<std::string()>;
+    StatsProvider mStatsProvider;
+
+private:
+    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
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/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/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index d9c04f2..e45e24fe 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -41,6 +41,15 @@
   repeated DimensionsValue dimensions_value = 1;
 }
 
+message StateValue {
+  optional int32 atom_id = 1;
+
+  oneof contents {
+    int64 group_id = 2;
+    int32 value = 3;
+  }
+}
+
 message EventMetricData {
   optional int64 elapsed_timestamp_nanos = 1;
 
@@ -66,12 +75,14 @@
 message CountMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+  repeated StateValue slice_by_state = 6;
 
   repeated CountBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
   repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 1b7f398..c107397 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -150,6 +150,24 @@
   }
 }
 
+message StateMap {
+  message StateGroup {
+    optional int64 group_id = 1;
+
+    repeated int32 value = 2;
+  }
+
+  repeated StateGroup group = 1;
+}
+
+message State {
+  optional int64 id = 1;
+
+  optional int32 atom_id = 2;
+
+  optional StateMap map = 3;
+}
+
 message MetricConditionLink {
   optional int64 condition = 1;
 
@@ -158,6 +176,14 @@
   optional FieldMatcher fields_in_condition = 3;
 }
 
+message MetricStateLink {
+  optional int64 state = 1;
+
+  optional FieldMatcher fields_in_what = 2;
+
+  optional FieldMatcher fields_in_state = 3;
+}
+
 message FieldFilter {
   optional bool include_all = 1 [default = false];
   optional FieldMatcher fields = 2;
@@ -182,11 +208,15 @@
 
   optional FieldMatcher dimensions_in_what = 4;
 
-  optional FieldMatcher dimensions_in_condition = 7 [deprecated = true];
+  repeated int64 slice_by_state = 8;
 
   optional TimeUnit bucket = 5;
 
   repeated MetricConditionLink links = 6;
+
+  repeated MetricStateLink state_link = 9;
+
+  optional FieldMatcher dimensions_in_condition = 7 [deprecated = true];
 }
 
 message DurationMetric {
@@ -438,6 +468,8 @@
 
   optional bool persist_locally = 20 [default = false];
 
+  repeated State state = 21;
+
   // Field number 1000 is reserved for later use.
   reserved 1000;
 }
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/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index fe25a25..76ee9a6 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -1713,6 +1713,11 @@
     EXPECT_EQ(kActive, activation1004->state);
     EXPECT_EQ(ACTIVATE_IMMEDIATELY, activation1004->activationType);
     // }}}------------------------------------------------------------------------------
+
+    // Clear the data stored on disk as a result of the system server death.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey1, configAddedTimeNs + NS_PER_SEC, false, true,
+                                ADB_DUMP, FAST, &buffer);
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
index b98dc60..325e869 100644
--- a/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/ConfigTtl_e2e_test.cpp
@@ -96,6 +96,11 @@
 
     EXPECT_EQ((int64_t)(bucketStartTimeNs + 25 * bucketSizeNs + 2 + 2 * 3600 * NS_PER_SEC),
               processor->mMetricsManagers.begin()->second->getTtlEndNs());
+
+    // Clear the data stored on disk as a result of the ttl.
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 25 * bucketSizeNs + 3, false, true,
+                                ADB_DUMP, FAST, &buffer);
 }
 
 
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
new file mode 100644
index 0000000..5c9636f
--- /dev/null
+++ b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceflingerStatsPuller_test"
+
+#include "src/external/SurfaceflingerStatsPuller.h"
+
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
+public:
+    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
+
+    void injectStats(const StatsProvider& statsProvider) {
+        mStatsProvider = statsProvider;
+    }
+};
+
+class SurfaceflingerStatsPullerTest : public ::testing::Test {
+public:
+    SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+
+    ~SurfaceflingerStatsPullerTest() {
+        const ::testing::TestInfo* const test_info =
+                ::testing::UnitTest::GetInstance()->current_test_info();
+        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+    }
+};
+
+TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
+    surfaceflinger::SFTimeStatsGlobalProto proto;
+    proto.set_total_frames(1);
+    proto.set_missed_frames(2);
+    proto.set_client_composition_frames(2);
+    proto.set_display_on_time(4);
+
+    auto bucketOne = proto.add_present_to_present();
+    bucketOne->set_time_millis(2);
+    bucketOne->set_frame_count(4);
+    auto bucketTwo = proto.add_present_to_present();
+    bucketTwo->set_time_millis(4);
+    bucketTwo->set_frame_count(1);
+    auto bucketThree = proto.add_present_to_present();
+    bucketThree->set_time_millis(1000);
+    bucketThree->set_frame_count(1);
+    static constexpr int64_t expectedAnimationMillis = 12;
+    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
+
+    puller.injectStats([&] {
+        return proto.SerializeAsString();
+    });
+    puller.ForceClearCache();
+    vector<std::shared_ptr<LogEvent>> outData;
+    puller.Pull(&outData);
+
+    ASSERT_EQ(1, outData.size());
+    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
+    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
+    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
+    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
+    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
+    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
+}
+
+}  // 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/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.txt b/config/hiddenapi-greylist.txt
index b4913c8..e1cf7c1 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -218,7 +218,6 @@
 Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ILocationManager;
 Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I
 Landroid/location/INetInitiatedListener$Stub;-><init>()V
-Landroid/location/LocationManager$ListenerTransport;-><init>(Landroid/location/LocationManager;Landroid/location/LocationListener;Landroid/os/Looper;)V
 Landroid/Manifest$permission;->CAPTURE_SECURE_VIDEO_OUTPUT:Ljava/lang/String;
 Landroid/Manifest$permission;->CAPTURE_VIDEO_OUTPUT:Ljava/lang/String;
 Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String;
@@ -1150,6 +1149,8 @@
 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
@@ -1157,6 +1158,7 @@
 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;
@@ -1180,4 +1182,284 @@
 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/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/mms/pdu/PduParser;->$assertionsDisabled:Z
 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 28c1dea..0000000
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
+++ /dev/null
@@ -1,156 +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;
-    }
-
-    @NonNull
-    @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(@NonNull 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 287a289..cf36032 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -946,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. */
@@ -1921,11 +1925,15 @@
     }
 
     /**
-     * Like {@link #isVoiceInteraction}, but only returns true if this is also the root
-     * of a voice interaction.  That is, returns true if this activity was directly
+     * Like {@link #isVoiceInteraction}, but only returns {@code true} if this is also the root
+     * of a voice interaction.  That is, returns {@code true} if this activity was directly
      * started by the voice interaction service as the initiation of a voice interaction.
      * Otherwise, for example if it was started by another activity while under voice
-     * interaction, returns false.
+     * interaction, returns {@code false}.
+     * If the activity {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode} is
+     * {@code singleTask}, it forces the activity to launch in a new task, separate from the one
+     * that started it. Therefore, there is no longer a relationship between them, and
+     * {@link #isVoiceInteractionRoot()} return {@code false} in this case.
      */
     public boolean isVoiceInteractionRoot() {
         try {
@@ -2469,17 +2477,13 @@
             getAutofillManager().onInvisibleForAutofill();
         }
 
-        if (isFinishing()) {
-            if (mAutoFillResetNeeded) {
-                getAutofillManager().onActivityFinishing();
-            } else if (mIntent != null
-                    && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
-                // Activity was launched when user tapped a link in the Autofill Save UI - since
-                // user launched another activity, the Save UI should not be restored when this
-                // activity is finished.
-                getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
-                        mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
-            }
+        if (isFinishing() && !mAutoFillResetNeeded && mIntent != null
+                && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
+            // Activity was launched when user tapped a link in the Autofill Save UI - since
+            // user launched another activity, the Save UI should not be restored when this
+            // activity is finished.
+            getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
+                    mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
         }
         mEnterAnimationComplete = false;
     }
@@ -2517,6 +2521,10 @@
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
         mCalled = true;
 
+        if (isFinishing() && mAutoFillResetNeeded) {
+            getAutofillManager().onActivityFinishing();
+        }
+
         // dismiss any dialogs we are managing.
         if (mManagedDialogs != null) {
             final int numDialogs = mManagedDialogs.size();
@@ -7003,6 +7011,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=");
@@ -8084,6 +8096,7 @@
         if (mWindow != null) {
             mWindow.onMultiWindowModeChanged();
         }
+        mLastDispatchedIsInMultiWindowMode = isInMultiWindowMode;
         onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
@@ -8096,6 +8109,7 @@
         if (mWindow != null) {
             mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
         }
+        mLastDispatchedIsInPictureInPictureMode = isInPictureInPictureMode;
         onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
     }
 
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 bb87d96..1649e8b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -853,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";
@@ -1134,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
@@ -1180,6 +1184,7 @@
             // Storage
             OP_READ_EXTERNAL_STORAGE,
             OP_WRITE_EXTERNAL_STORAGE,
+            OP_ACCESS_MEDIA_LOCATION,
             // Location
             OP_COARSE_LOCATION,
             OP_FINE_LOCATION,
@@ -1218,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
     };
 
     /**
@@ -1319,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
     };
 
@@ -1416,6 +1425,7 @@
             OPSTR_LEGACY_STORAGE,
             OPSTR_ACCESS_ACCESSIBILITY,
             OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_ACCESS_MEDIA_LOCATION,
             OPSTR_QUERY_ALL_PACKAGES,
     };
 
@@ -1514,6 +1524,7 @@
             "LEGACY_STORAGE",
             "ACCESS_ACCESSIBILITY",
             "READ_DEVICE_IDENTIFIERS",
+            "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
     };
 
@@ -1598,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,
@@ -1613,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
     };
 
@@ -1712,6 +1724,7 @@
             null, // LEGACY_STORAGE
             null, // ACCESS_ACCESSIBILITY
             null, // READ_DEVICE_IDENTIFIERS
+            null, // ACCESS_MEDIA_LOCATION
             null, // QUERY_ALL_PACKAGES
     };
 
@@ -1810,6 +1823,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1907,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
     };
 
@@ -2008,6 +2023,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -2668,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);
         }
 
@@ -2690,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);
         }
 
@@ -4251,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.
      */
@@ -4281,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;
         }
@@ -4313,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;
         }
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index 64f886a..df6533a 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -238,7 +238,7 @@
             time = 1566503083973L,
             codegenVersion = "1.0.0",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=90L) 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)")
+            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/Notification.java b/core/java/android/app/Notification.java
index bb4e998..efb9f6bb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -8150,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.
@@ -8204,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;
@@ -10505,12 +10511,7 @@
         final StandardTemplateParams fillTextsFrom(Builder b) {
             Bundle extras = b.mN.extras;
             this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE));
-
-            CharSequence text = extras.getCharSequence(EXTRA_BIG_TEXT);
-            if (TextUtils.isEmpty(text)) {
-                text = extras.getCharSequence(EXTRA_TEXT);
-            }
-            this.text = b.processLegacyText(text);
+            this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
             this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT);
             return this;
         }
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/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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a136bbd..ae87216 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 =
@@ -2078,7 +2079,8 @@
         ID_TYPE_BASE_INFO,
         ID_TYPE_SERIAL,
         ID_TYPE_IMEI,
-        ID_TYPE_MEID
+        ID_TYPE_MEID,
+        ID_TYPE_INDIVIDUAL_ATTESTATION
     })
     public @interface AttestationIdType {}
 
@@ -2113,6 +2115,14 @@
     public static final int ID_TYPE_MEID = 8;
 
     /**
+     * Specifies that the device should attest using an individual attestation certificate.
+     * For use with {@link #generateKeyPair}.
+     *
+     * @see #generateKeyPair
+     */
+    public static final int ID_TYPE_INDIVIDUAL_ATTESTATION = 16;
+
+    /**
      * Service-specific error code for {@link #generateKeyPair}:
      * Indicates the call has failed due to StrongBox unavailability.
      * @hide
@@ -2531,14 +2541,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 +2619,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 +3196,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.
@@ -4894,24 +4901,47 @@
      * have been given to access the key and certificates associated with this alias will be
      * revoked.
      *
+     * <p>Attestation: to enable attestation, set an attestation challenge in {@code keySpec} via
+     * {@link KeyGenParameterSpec.Builder#setAttestationChallenge}. By specifying flags to the
+     * {@code idAttestationFlags} parameter, it is possible to request the device's unique
+     * identity to be included in the attestation record.
+     *
+     * <p>Specific identifiers can be included in the attestation record, and an individual
+     * attestation certificate can be used to sign the attestation record. To find out if the device
+     * supports these features, refer to {@link #isDeviceIdAttestationSupported()} and
+     * {@link #isUniqueDeviceAttestationSupported()}.
+     *
+     * <p>Device owner, profile owner and their delegated certificate installer can use
+     * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
+     * including manufacturer, model, brand, device and product in the attestation record.
+     * Only device owner and their delegated certificate installer can use
+     * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request
+     * unique device identifiers to be attested (the serial number, IMEI and MEID correspondingly),
+     * if supported by the device (see {@link #isDeviceIdAttestationSupported()}).
+     * Additionally, device owner and their delegated certificate installer can also request the
+     * attestation record to be signed using an individual attestation certificate by specifying
+     * the {@link #ID_TYPE_INDIVIDUAL_ATTESTATION} flag (if supported by the device, see
+     * {@link #isUniqueDeviceAttestationSupported()}).
+     * <p>
+     * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
+     * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
+     * <p>
+     * Attestation using {@link #ID_TYPE_INDIVIDUAL_ATTESTATION} can only be requested if
+     * key generation is done in StrongBox.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *            {@code null} if calling from a delegated certificate installer.
      * @param algorithm The key generation algorithm, see {@link java.security.KeyPairGenerator}.
      * @param keySpec Specification of the key to generate, see
      * {@link java.security.KeyPairGenerator}.
-     * @param idAttestationFlags A bitmask of all the identifiers that should be included in the
+     * @param idAttestationFlags A bitmask of the identifiers that should be included in the
      *        attestation record ({@code ID_TYPE_BASE_INFO}, {@code ID_TYPE_SERIAL},
-     *        {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), or {@code 0} if no device
-     *        identification is required in the attestation record.
-     *        Device owner, profile owner and their delegated certificate installer can use
-     *        {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information
-     *        including manufacturer, model, brand, device and product in the attestation record.
-     *        Only device owner and their delegated certificate installer can use
-     *        {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request
-     *        unique device identifiers to be attested.
+     *        {@code ID_TYPE_IMEI} and {@code ID_TYPE_MEID}), and
+     *        {@code ID_TYPE_INDIVIDUAL_ATTESTATION} if the attestation record should be signed
+     *        using an individual attestation certificate.
      *        <p>
-     *        If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
-     *        is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
+     *        {@code 0} should be passed in if no device identification is required in the
+     *        attestation record and the batch attestation certificate should be used.
      *        <p>
      *        If any flag is specified, then an attestation challenge must be included in the
      *        {@code keySpec}.
@@ -5053,7 +5083,8 @@
 
     /**
      * Returns {@code true} if the device supports attestation of device identifiers in addition
-     * to key attestation.
+     * to key attestation. See
+     * {@link #generateKeyPair(ComponentName, String, KeyGenParameterSpec, int)}
      * @return {@code true} if Device ID attestation is supported.
      */
     public boolean isDeviceIdAttestationSupported() {
@@ -5062,6 +5093,20 @@
     }
 
     /**
+     * Returns {@code true} if the StrongBox Keymaster implementation on the device was provisioned
+     * with an individual attestation certificate and can sign attestation records using it (as
+     * attestation using an individual attestation certificate is a feature only Keymaster
+     * implementations with StrongBox security level can implement).
+     * For use prior to calling
+     * {@link #generateKeyPair(ComponentName, String, KeyGenParameterSpec, int)}.
+     * @return {@code true} if individual attestation is supported.
+     */
+    public boolean isUniqueDeviceAttestationSupported() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_UNIQUE_ATTESTATION);
+    }
+
+    /**
      * Called by a device or profile owner, or delegated certificate installer, to associate
      * certificates with a key pair that was generated using {@link #generateKeyPair}, and
      * set whether the key is available for the user to choose in the certificate selection
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/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/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/Context.java b/core/java/android/content/Context.java
index b612f1c..802c1a0a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4708,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 3418b7b..72204da 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -10004,7 +10004,10 @@
         if (!Objects.equals(this.mData, other.mData)) return false;
         if (!Objects.equals(this.mType, other.mType)) return false;
         if (!Objects.equals(this.mIdentifier, other.mIdentifier)) return false;
-        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
+        if (!(this.hasPackageEquivalentComponent() && other.hasPackageEquivalentComponent())
+                && !Objects.equals(this.mPackage, other.mPackage)) {
+            return false;
+        }
         if (!Objects.equals(this.mComponent, other.mComponent)) return false;
         if (!Objects.equals(this.mCategories, other.mCategories)) return false;
 
@@ -10012,6 +10015,15 @@
     }
 
     /**
+     * Return {@code true} if the component name is not null and is in the same package that this
+     * intent limited to. otherwise return {@code false}.
+     */
+    private boolean hasPackageEquivalentComponent() {
+        return mComponent != null
+            && (mPackage == null || mPackage.equals(mComponent.getPackageName()));
+    }
+
+    /**
      * Generate hash code that matches semantics of filterEquals().
      *
      * @return Returns the hash value of the action, data, type, class, and
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/pm/AndroidTelephonyCommonUpdater.java b/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
deleted file mode 100644
index 1a720d5..0000000
--- a/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
+++ /dev/null
@@ -1,82 +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.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 c74daa8..3933e81 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -519,9 +519,9 @@
      * <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 <code>&lt;application&gt;</code> tag in the app's manifest doesn't contain any child
-     * elements that represent
-     * <a href="/guide/components/fundamentals#DeclaringComponents">app components</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
diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 797ba64b..4331bd4 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -51,8 +51,6 @@
 
         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..fafb56d 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;
@@ -2849,6 +2849,17 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device supports device-unique Keystore attestations.  Only available on devices that
+     * also support {@link #FEATURE_STRONGBOX_KEYSTORE}, and can only be used by device owner
+     * apps (see {@link android.app.admin.DevicePolicyManager#generateKeyPair}).
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_DEVICE_UNIQUE_ATTESTATION =
+            "android.hardware.device_unique_attestation";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device has a Keymaster implementation that supports Device ID attestation.
      *
      * @see DevicePolicyManager#isDeviceIdAttestationSupported
@@ -3078,8 +3089,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 +3216,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 +3267,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 +3406,7 @@
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    @Disabled
     public static final long FILTER_APPLICATION_QUERY = 135549675L;
 
     /** {@hide} */
@@ -4017,7 +4044,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 +7114,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 +7124,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..f28b85c 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;
@@ -307,6 +308,17 @@
     public abstract String getNameForUid(int uid);
 
     /**
+     * Marks a package as installed (or not installed) for a given user.
+     *
+     * @param pkg the package whose installation is to be set
+     * @param userId the user for whom to set it
+     * @param installed the new installed state
+     * @return true if the installed state changed as a result
+     */
+    public abstract boolean setInstalled(PackageParser.Package pkg,
+            @UserIdInt int userId, boolean installed);
+
+    /**
      * Request to perform the second phase of ephemeral resolution.
      * @param responseObj The response of the first phase of ephemeral resolution
      * @param origIntent The original intent that triggered ephemeral resolution
@@ -321,25 +333,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 4c970da..0b157fa 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -4070,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,
diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java
index 4c66fc0..a607a9f 100644
--- a/core/java/android/content/pm/SharedLibraryNames.java
+++ b/core/java/android/content/pm/SharedLibraryNames.java
@@ -33,6 +33,4 @@
     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/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d03bea2..df652f1 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;
@@ -126,6 +126,13 @@
     public static final int FLAG_SYSTEM = 0x00000800;
 
     /**
+     * Indicates that this user is some sort of profile. Right now, the only profile type is
+     * {@link #FLAG_MANAGED_PROFILE}, but this can include other types of profiles too if any
+     * are created in the future. This is therefore not a flag, but an OR of several flags.
+     */
+    public static final int PROFILE_FLAGS_MASK = FLAG_MANAGED_PROFILE;
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = "FLAG_", value = {
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 0714884..fb1ece2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -567,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,
@@ -1249,7 +1252,9 @@
      *
      * <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.</p>
+     * 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>
@@ -1275,7 +1280,7 @@
      * <p>Application can use this method to retrieve the system-wide camera audio restriction
      * settings described in {@link #setCameraAudioRestriction}.</p>
      *
-     * @return The system-wide mute mode setting resulting from this call
+     * @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
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 3f56dfa..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) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 4494d27..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() {
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/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/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 3cd9eb8..c8dbd16 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -55,6 +55,18 @@
      */
     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);
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/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 271020d..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;
 
@@ -1023,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/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/UserManager.java b/core/java/android/os/UserManager.java
index baf748f..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
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/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index eaf9929..9a3a7ce 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -576,6 +576,8 @@
             argsForZygote.add("--mount-external-installer");
         } else if (mountExternal == Zygote.MOUNT_EXTERNAL_LEGACY) {
             argsForZygote.add("--mount-external-legacy");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
+            argsForZygote.add("--mount-external-pass-through");
         }
 
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
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/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/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/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 7115da2..61d8eb1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2103,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();
@@ -7885,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
          */
@@ -8811,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
@@ -12927,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";
@@ -12942,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/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/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/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/text/TextLine.java b/core/java/android/text/TextLine.java
index 5a2d8f4..1c50d73 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -276,7 +276,7 @@
         final int runCount = mDirections.getRunCount();
         for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart >= mLen) break;
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -361,7 +361,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
-            if (runStart >= mLen) break;
+            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/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..f4bee57 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
@@ -58,14 +55,6 @@
     void onImeVisibilityChanged(boolean imeVisible, int imeHeight);
 
     /**
-     * Called when window manager decides to adjust the pinned stack bounds because of the shelf, or
-     * when the listener is first registered to allow the listener to synchronized its state with
-     * the controller.  This call will always be followed by a onMovementBoundsChanged() call
-     * with fromShelfAdjustment set to {@code true}.
-     */
-    void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight);
-
-    /**
      * Called when window manager decides to adjust the minimized state, or when the listener
      * is first registered to allow the listener to synchronized its state with the controller.
      */
@@ -76,4 +65,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..49e8800 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -326,12 +326,6 @@
     oneway void setPipVisibility(boolean visible);
 
     /**
-     * Called by System UI to notify of changes to the visibility and height of the shelf.
-     */
-    @UnsupportedAppUsage
-    void setShelfHeight(boolean visible, int shelfHeight);
-
-    /**
      * Called by System UI to enable or disable haptic feedback on the navigation bar buttons.
      */
     @UnsupportedAppUsage
@@ -640,4 +634,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/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/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 90e69f3..db3ef20 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;
@@ -667,9 +675,9 @@
                 mTmpTransaction.remove(mBackgroundControl);
                 mBackgroundControl = null;
             }
+            mSurface.release();
             mTmpTransaction.apply();
         }
-        mSurfaceAlpha = 1f;
     }
 
     /** @hide */
@@ -955,7 +963,6 @@
                 } finally {
                     mIsCreating = false;
                     if (mSurfaceControl != null && !mSurfaceCreated) {
-                        mSurface.release();
                         releaseSurfaces();
                     }
                 }
@@ -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,19 @@
                         frameNumber);
             }
             mRtTransaction.hide(mSurfaceControl);
+
+            synchronized (mSurfaceControlLock) {
+                if (mRtReleaseSurfaces) {
+                    mRtReleaseSurfaces = false;
+                    mRtTransaction.remove(mSurfaceControl);
+                    mRtTransaction.remove(mBackgroundControl);
+                    mSurfaceControl = null;
+                    mBackgroundControl = null;
+                    mSurface.release();
+                }
+                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 7ee3973..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;
@@ -9925,6 +9930,7 @@
         info.setImportantForAccessibility(isImportantForAccessibility());
         info.setPackageName(mContext.getPackageName());
         info.setClassName(getAccessibilityClassName());
+        info.setStateDescription(getStateDescription());
         info.setContentDescription(getContentDescription());
 
         info.setEnabled(isEnabled());
@@ -10297,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
@@ -10315,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
@@ -14414,8 +14466,8 @@
         }
 
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-        if (isVisible != oldVisible) {
-            updateSystemGestureExclusionRects();
+        if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
+            postUpdateSystemGestureExclusionRects();
         }
     }
 
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 85aba3c..3756a7d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1997,7 +1997,6 @@
         mIsInTraversal = true;
         mWillDrawSoon = true;
         boolean windowSizeMayChange = false;
-        final boolean windowAttributesChanged = mWindowAttributesChanged;
         WindowManager.LayoutParams lp = mWindowAttributes;
 
         int desiredWindowWidth;
@@ -2014,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) {
@@ -2194,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;
@@ -2259,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;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b2d7032..001ab66 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1475,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;
 
@@ -1494,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;
 
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 61debc8..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);
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/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/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 de77aaa..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;
 
@@ -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/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/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 9049c3a..e415b41 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -33,15 +33,30 @@
      * Reports that a compatibility change is affecting an app process now.
      *
      * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, ApplicationInfo)},
-     * you do not need to call this API directly. The change will be reported for you in the case
-     * that {@link #isChangeEnabled(long, ApplicationInfo)} returns {@code true}.
+     * you do not need to call this API directly. The change will be reported for you.
      *
      * @param changeId The ID of the compatibility change taking effect.
-     * @param appInfo Representing the affected app.
+     * @param appInfo  Representing the affected app.
      */
     void reportChange(long changeId, in ApplicationInfo appInfo);
 
     /**
+     * Reports that a compatibility change is affecting an app process now.
+     *
+     * <p>Same as {@link #reportChange(long, ApplicationInfo)}, except it receives a package name
+     * instead of an {@link ApplicationInfo}
+     * object, and finds an app info object based on the package name. Returns {@code true} if
+     * there is no installed package by that name.
+     *
+     * <p>Note: for changes that are gated using {@link #isChangeEnabled(long, String)},
+     * you do not need to call this API directly. The change will be reported for you.
+     *
+     * @param changeId    The ID of the compatibility change taking effect.
+     * @param packageName The package name of the app in question.
+     */
+     void reportChangeByPackageName(long changeId, in String packageName);
+
+    /**
      * Query if a given compatibility change is enabled for an app process. This method should
      * be called when implementing functionality on behalf of the affected app.
      *
@@ -49,13 +64,35 @@
      * change, resulting in differing behaviour compared to earlier releases. If this method returns
      * {@code false}, the calling code should behave as it did in earlier releases.
      *
-     * <p>When this method returns {@code true}, it will also report the change as
-     * {@link #reportChange(long, ApplicationInfo)} would, so there is no need to call that method
-     * directly.
+     * <p>It will also report the change as {@link #reportChange(long, ApplicationInfo)} would, so
+     * there is no need to call that method directly.
      *
      * @param changeId The ID of the compatibility change in question.
-     * @param appInfo Representing the app in question.
+     * @param appInfo  Representing the app in question.
      * @return {@code true} if the change is enabled for the current app.
      */
     boolean isChangeEnabled(long changeId, in ApplicationInfo appInfo);
+
+    /**
+     * Query if a given compatibility change is enabled for an app process. This method should
+     * be called when implementing functionality on behalf of the affected app.
+     *
+     * <p>Same as {@link #isChangeEnabled(long, ApplicationInfo)}, except it receives a package name
+     * instead of an {@link ApplicationInfo}
+     * object, and finds an app info object based on the package name. Returns {@code true} if
+     * there is no installed package by that name.
+     *
+     * <p>If this method returns {@code true}, the calling code should implement the compatibility
+     * change, resulting in differing behaviour compared to earlier releases. If this method
+     * returns
+     * {@code false}, the calling code should behave as it did in earlier releases.
+     *
+     * <p>It will also report the change as {@link #reportChange(long, String)} would, so there is
+     * no need to call that method directly.
+     *
+     * @param changeId    The ID of the compatibility change in question.
+     * @param packageName The package name of the app in question.
+     * @return {@code true} if the change is enabled for the current app.
+     */
+    boolean isChangeEnabledByPackageName(long changeId, in String packageName);
 }
\ No newline at end of file
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/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 9d4cdc7..3ce3838 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -135,6 +135,9 @@
     /** Read-write external storage should be mounted instead of package sandbox */
     public static final int MOUNT_EXTERNAL_FULL = IVold.REMOUNT_MODE_FULL;
 
+    /** The lower file system should be bind mounted directly on external storage */
+    public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
     /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
     static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
 
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index abc4160..a23e659 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -362,6 +362,8 @@
                 mMountExternal = Zygote.MOUNT_EXTERNAL_INSTALLER;
             }  else if (arg.equals("--mount-external-legacy")) {
                 mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
+            } else if (arg.equals("--mount-external-pass-through")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
             } else if (arg.equals("--query-abi-list")) {
                 mAbiListQuery = true;
             } else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 31995f7..3be1a1a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -749,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",
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/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/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 7cd3e95..697825d 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -35,6 +35,7 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
@@ -50,6 +51,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Loads global system configuration info.
@@ -209,6 +211,10 @@
 
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
 
+    // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
+    private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
+    private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
+
     public static SystemConfig getInstance() {
         if (!isSystemProcess()) {
             Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
@@ -359,7 +365,48 @@
         return mBugreportWhitelistedPackages;
     }
 
+    /**
+     * Gets map of packagesNames to userTypes, dictating on which user types each package should be
+     * initially installed, and then removes this map from SystemConfig.
+     * Called by UserManagerService when it is constructed.
+     */
+    public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+        ArrayMap<String, Set<String>> r = mPackageToUserTypeWhitelist;
+        mPackageToUserTypeWhitelist = new ArrayMap<>(0);
+        return r;
+    }
+
+    /**
+     * Gets map of packagesNames to userTypes, dictating on which user types each package should NOT
+     * be initially installed, even if they are whitelisted, and then removes this map from
+     * SystemConfig.
+     * Called by UserManagerService when it is constructed.
+     */
+    public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+        ArrayMap<String, Set<String>> r = mPackageToUserTypeBlacklist;
+        mPackageToUserTypeBlacklist = new ArrayMap<>(0);
+        return r;
+    }
+
+    /**
+     * Only use for testing. Do NOT use in production code.
+     * @param readPermissions false to create an empty SystemConfig; true to read the permissions.
+     */
+    @VisibleForTesting
+    protected SystemConfig(boolean readPermissions) {
+        if (readPermissions) {
+            Slog.w(TAG, "Constructing a test SystemConfig");
+            readAllPermissions();
+        } else {
+            Slog.w(TAG, "Constructing an empty test SystemConfig");
+        }
+    }
+
     SystemConfig() {
+        readAllPermissions();
+    }
+
+    private void readAllPermissions() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
                 Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
@@ -419,7 +466,8 @@
                 Environment.getSystemExtDirectory(), "etc", "permissions"), ALLOW_ALL);
     }
 
-    void readPermissions(File libraryDir, int permissionFlag) {
+    @VisibleForTesting
+    public void readPermissions(File libraryDir, int permissionFlag) {
         // Read permissions from given directory.
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             if (permissionFlag == ALLOW_ALL) {
@@ -954,6 +1002,11 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "install-in-user-type": {
+                        // NB: We allow any directory permission to declare install-in-user-type.
+                        readInstallInUserType(parser,
+                                mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist);
+                    } break;
                     default: {
                         Slog.w(TAG, "Tag " + name + " is unknown in "
                                 + permFile + " at " + parser.getPositionDescription());
@@ -1091,6 +1144,53 @@
         }
     }
 
+    private void readInstallInUserType(XmlPullParser parser,
+            Map<String, Set<String>> doInstallMap,
+            Map<String, Set<String>> nonInstallMap)
+            throws IOException, XmlPullParserException {
+        final String packageName = parser.getAttributeValue(null, "package");
+        if (TextUtils.isEmpty(packageName)) {
+            Slog.w(TAG, "package is required for <install-in-user-type> in "
+                    + parser.getPositionDescription());
+            return;
+        }
+
+        Set<String> userTypesYes = doInstallMap.get(packageName);
+        Set<String> userTypesNo = nonInstallMap.get(packageName);
+        final int depth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, depth)) {
+            final String name = parser.getName();
+            if ("install-in".equals(name)) {
+                final String userType = parser.getAttributeValue(null, "user-type");
+                if (TextUtils.isEmpty(userType)) {
+                    Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+                            + parser.getPositionDescription());
+                    continue;
+                }
+                if (userTypesYes == null) {
+                    userTypesYes = new ArraySet<>();
+                    doInstallMap.put(packageName, userTypesYes);
+                }
+                userTypesYes.add(userType);
+            } else if ("do-not-install-in".equals(name)) {
+                final String userType = parser.getAttributeValue(null, "user-type");
+                if (TextUtils.isEmpty(userType)) {
+                    Slog.w(TAG, "user-type is required for <install-in-user-type> in "
+                            + parser.getPositionDescription());
+                    continue;
+                }
+                if (userTypesNo == null) {
+                    userTypesNo = new ArraySet<>();
+                    nonInstallMap.put(packageName, userTypesNo);
+                }
+                userTypesNo.add(userType);
+            } else {
+                Slog.w(TAG, "unrecognized tag in <install-in-user-type> in "
+                        + parser.getPositionDescription());
+            }
+        }
+    }
+
     void readOemPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
         final String packageName = parser.getAttributeValue(null, "package");
         if (TextUtils.isEmpty(packageName)) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 7779f55..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: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c12940a..d46fe8d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -696,26 +696,32 @@
     // Read if we are using the profile configuration, do this at the start since the last ART args
     // take precedence.
     property_get("dalvik.vm.profilebootclasspath", propBuf, "");
-    std::string profile_boot_class_path = propBuf;
+    std::string profile_boot_class_path_flag = propBuf;
     // Empty means the property is unset and we should default to the phenotype property.
     // The possible values are {"true", "false", ""}
-    if (profile_boot_class_path.empty()) {
-        profile_boot_class_path = server_configurable_flags::GetServerConfigurableFlag(
+    if (profile_boot_class_path_flag.empty()) {
+        profile_boot_class_path_flag = server_configurable_flags::GetServerConfigurableFlag(
                 RUNTIME_NATIVE_BOOT_NAMESPACE,
                 PROFILE_BOOT_CLASS_PATH,
                 /*default_value=*/ "");
     }
-    if (profile_boot_class_path == "true") {
+    const bool profile_boot_class_path = (profile_boot_class_path_flag == "true");
+    if (profile_boot_class_path) {
+        addOption("-Xcompiler-option");
+        addOption("--count-hotness-in-compiled-code");
         addOption("-Xps-profile-boot-class-path");
         addOption("-Xps-profile-aot-code");
         addOption("-Xjitsaveprofilinginfo");
     }
 
-    std::string use_apex_image =
+    std::string use_apex_image_flag =
         server_configurable_flags::GetServerConfigurableFlag(RUNTIME_NATIVE_BOOT_NAMESPACE,
                                                              ENABLE_APEX_IMAGE,
                                                              /*default_value=*/ "");
-    if (use_apex_image == "true") {
+    // Use the APEX boot image for boot class path profiling to get JIT samples on BCP methods.
+    // Also use the APEX boot image if it's explicitly enabled via configuration flag.
+    const bool use_apex_image = profile_boot_class_path || (use_apex_image_flag == "true");
+    if (use_apex_image) {
         addOption(kApexImageOption);
         ALOGI("Using Apex boot image: '%s'\n", kApexImageOption);
     } else if (parseRuntimeOption("dalvik.vm.boot-image", bootImageBuf, "-Ximage:")) {
@@ -1163,9 +1169,15 @@
         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;
+    }
+
+    const char* i18nRootDir = getenv("ANDROID_I18N_ROOT");
+    if (i18nRootDir == NULL) {
+        LOG_FATAL("No runtime directory specified with ANDROID_I18N_ROOT environment variable.");
         return;
     }
 
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_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 cf8df28..f2a4f4f 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>
@@ -257,6 +257,8 @@
             which_heap = HEAP_NATIVE;
         } else if (base::StartsWith(name, "[anon:libc_malloc]")) {
             which_heap = HEAP_NATIVE;
+        } else if (base::StartsWith(name, "[anon:scudo:")) {
+            which_heap = HEAP_NATIVE;
         } else if (base::StartsWith(name, "[stack")) {
             which_heap = HEAP_STACK;
         } else if (base::StartsWith(name, "[anon:stack_and_tls:")) {
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_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/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d5b875b..93ef751 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>
@@ -303,7 +303,8 @@
   MOUNT_EXTERNAL_LEGACY = 4,
   MOUNT_EXTERNAL_INSTALLER = 5,
   MOUNT_EXTERNAL_FULL = 6,
-  MOUNT_EXTERNAL_COUNT = 7
+  MOUNT_EXTERNAL_PASS_THROUGH = 7,
+  MOUNT_EXTERNAL_COUNT = 8
 };
 
 // The order of entries here must be kept in sync with MountExternalKind enum values.
@@ -708,15 +709,14 @@
 
   const userid_t user_id = multiuser_get_user_id(uid);
   const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
+  const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
   bool isFuse = GetBoolProperty(kPropFuse, false);
 
   CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
 
   if (isFuse) {
-    // TODO(b/135341433): Bind mount the appropriate storage view for the app given its permissions
-    // media and media_location permission access. This should prevent the kernel from incorrectly
-    // sharing a cache across permission buckets
-    BindMount(user_source, "/storage", fail_fn);
+    BindMount(mount_mode == MOUNT_EXTERNAL_PASS_THROUGH ? pass_through_source : user_source,
+              "/storage", fail_fn);
   } else {
     const std::string& storage_source = ExternalStorageViews[mount_mode];
     BindMount(storage_source, "/storage", fail_fn);
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index e199dab..6119d71 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -30,9 +30,9 @@
 }
 
 java_library_host {
-    name: "windowmanager-log-proto",
+    name: "protolog-proto",
     srcs: [
-        "android/server/windowmanagerlog.proto"
+        "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/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..3512c0a
--- /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 sfixed32 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/windowmanagerlog.proto b/core/proto/android/server/windowmanagerlog.proto
deleted file mode 100644
index 5bee1bd..0000000
--- a/core/proto/android/server/windowmanagerlog.proto
+++ /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.
- */
-
-syntax = "proto2";
-
-package com.android.server.wm;
-
-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 window manager log entries.
-   Encoded, it should start with 0x9 0x57 0x49 0x4e 0x44 0x4f 0x4c 0x4f 0x47 (.WINDOLOG), such
-   that they can be easily identified. */
-message WindowManagerLogFileProto {
-    /* 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 = 0x444e4957; /* WIND (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/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/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 ccd91a4..9185bae 100644
--- a/core/res/TEST_MAPPING
+++ b/core/res/TEST_MAPPING
@@ -5,6 +5,9 @@
             "options": [
                 {
                     "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 9ba0481..a44b137a 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1890,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 013537c..db758e3 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/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-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 2bef980..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>
@@ -1346,7 +1346,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"جميع الشبكات"</string>
     <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_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>
@@ -2026,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 446575b..00617a5 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/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-az/strings.xml b/core/res/res/values-az/strings.xml
index 6e93a97..f79decd 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1890,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 e14139c..10dc339 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1924,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 a71eef0..ce65bea 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1302,7 +1302,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Падключыцца"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Усе сеткі"</string>
     <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_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>
@@ -1389,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>
@@ -1720,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>
@@ -1958,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 7f5a3cc..c0843ea 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1891,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 bc84e93..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>
@@ -1380,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>
@@ -1926,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 8334eca..d828844 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1890,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 0d05709..2b524ae 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1958,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 70a4646..4d57a90 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1890,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 c7e381c..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>
@@ -1890,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 18af40d..6998c0c 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1675,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>
@@ -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-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 0229cf5..7346bd0 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1890,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 d9864fb..6c1c992 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1357,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>
@@ -1672,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>
@@ -1890,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 afa8453..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>
@@ -1672,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>
@@ -1890,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 9932fdf..9460ee7 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1638,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>
@@ -1891,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 cf7f37c..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>
@@ -1810,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>
@@ -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>
@@ -2006,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 d39bdc7..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>
@@ -1890,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 4eae9ba..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>
@@ -1672,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>
@@ -1890,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 1fd552e..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>
@@ -1890,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 919a13d..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>
@@ -1891,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 ecf717a..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>
@@ -1891,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 53e9217..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>
@@ -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-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 4908780..25007d5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1696,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>
@@ -1924,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 e32457a..0487601a 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1890,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 5af2235..e6223f0 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1356,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>
@@ -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>
@@ -1930,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 0ebabaf..3c6ee83 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1258,7 +1258,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Hubungkan"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua jaringan"</string>
     <string name="wifi_suggestion_title" msgid="6396033039578436801">"Izinkan jaringan Wi-Fi yang disarankan?"</string>
-    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> jaringan yang disarankan. Perangkat dapat terhubung secara otomatis."</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>
@@ -1890,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>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 296e4414..59cd4c1 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1891,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 501ef1a..cbc9989 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1890,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 a2031f8..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>
@@ -1958,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 a54d0ec..fe45c1f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1258,7 +1258,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"接続"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"すべてのネットワーク"</string>
     <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_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>
@@ -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-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 0376336..00afe026 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/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-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 68d63c7..81f512b 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1891,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 00dc422..539747b 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1892,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 0902271..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>
@@ -1891,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 669bb96..c0dd8b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1345,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>
@@ -1857,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>
@@ -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-ky/strings.xml b/core/res/res/values-ky/strings.xml
index e448a30..9182563 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1257,7 +1257,7 @@
     <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>
-    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Сунушталган Wi‑Fi тармактарына туташтырылсынбы?"</string>
+    <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>
@@ -1346,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>
@@ -1358,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>
@@ -1892,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 56e0a70..0189c08 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/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-lt/strings.xml b/core/res/res/values-lt/strings.xml
index e674c2a..0d29c84 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1958,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 96cb3cd..2547ba9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1924,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 39ad811..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>
@@ -1358,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>
@@ -1893,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 8588031..f711158 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1891,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 3672802..7570028 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/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-mr/strings.xml b/core/res/res/values-mr/strings.xml
index c58ae61..e22bca0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1673,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>
@@ -1891,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 1fd1d71..35e3658 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1258,7 +1258,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Sambung"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua rangkaian"</string>
     <string name="wifi_suggestion_title" msgid="6396033039578436801">"Benarkan rangkaian Wi-Fi yang dicadangkan?"</string>
-    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> rangkaian yang dicadangkan. Peranti mungkin disambungkan secara automatik."</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>
@@ -1890,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 4e91ca2..d69b7d1 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1257,7 +1257,7 @@
     <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>
-    <string name="wifi_suggestion_title" msgid="6396033039578436801">"အကြံပြုထားသည့် Wi‑Fi ကွန်ရက်များကို ခွင့်ပြုလိုပါသလား။"</string>
+    <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>
@@ -1891,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 6972a64..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>
@@ -1890,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 ef312a9..0cbaaa0 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1896,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 1a9797b..e898389 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1890,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 1da68a3..bd6d2cc 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/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="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 d39ea13..a9e93d4 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1891,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 a53608a..537089c 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1302,7 +1302,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Połącz"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Wszystkie sieci"</string>
     <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 połączyć się automatycznie."</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>
@@ -1958,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 f0d8d05..ba8285f 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1478,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>
@@ -1672,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>
@@ -1890,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 5d5618f..c7df3a7 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1357,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>
@@ -1890,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 f0d8d05..ba8285f 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1478,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>
@@ -1672,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>
@@ -1890,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 d790efc..7277228 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1924,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 0becae0..e458e46 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1301,7 +1301,7 @@
     <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>
-    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Разрешить подключение к предложенным сетям Wi‑Fi?"</string>
+    <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>
@@ -1720,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>
@@ -1958,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 a46da16..3e0f582 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1892,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 f996537..015cd91 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1302,7 +1302,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Pripojiť"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Všetky siete"</string>
     <string name="wifi_suggestion_title" msgid="6396033039578436801">"Chcete povoliť navrhované siete Wi‑Fi?"</string>
-    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Siete navrhnuté aplikáciou <xliff:g id="NAME">%s</xliff:g>. Zariadenie sa môže pripojiť automaticky."</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>
@@ -1958,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 b75644b..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>
@@ -1302,7 +1302,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Vzpostavi povezavo"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Vsa omrežja"</string>
     <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_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>
@@ -1539,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>
@@ -1958,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>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 2b1dd68..48ae4d7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1891,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 6404abc..75fc1ce 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1924,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 cf4f786..fdd0b7b 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1890,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 80fc4f8..c8c582f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1890,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 7028672..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,15 +356,15 @@
     <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="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>
@@ -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>
@@ -1891,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-te/strings.xml b/core/res/res/values-te/strings.xml
index aa5c271..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>
@@ -1257,8 +1257,8 @@
     <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>
-    <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_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>
@@ -1673,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>
@@ -1891,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 3472cb2..f2376d7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1258,7 +1258,7 @@
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"เชื่อมต่อ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"เครือข่ายทั้งหมด"</string>
     <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_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>
@@ -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-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 49a13c9..9acd72a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1890,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 dd75d23..e2bda9f 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1890,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 02c7957..ee52575 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1958,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 f7d03c5..5cc8db7 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1891,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 a6c1d3a..cb5191d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -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>
@@ -1891,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>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 59b55d0..e41dc43 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1672,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>
@@ -1890,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 d8b18d3..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>
@@ -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-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 1a73904..f0fce71 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/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-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 8f8b758..1f13208 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/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-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 4c37933..f707fcc 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1890,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 5fd53de..e3337b7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2527,6 +2527,16 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
+    <!-- Whether to only install system packages on a user if they're whitelisted for that user
+         type. These are flags and can be freely combined.
+         0 (0b000) - disable whitelist (install all system packages; no logging)
+         1 (0b001) - enforce (only install system packages if they are whitelisted)
+         2 (0b010) - log (log when a non-whitelisted package is run)
+         4 (0b100) - treat any package not mentioned in the whitelist file as implicitly whitelisted
+        Note: This list must be kept current with PACKAGE_WHITELIST_MODE_PROP in
+        frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java -->
+    <integer name="config_userTypePackageWhitelistMode">5</integer> <!-- 0b101 -->
+
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 18d7706..b53a399 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 -->
@@ -3629,6 +3634,11 @@
     <!-- Message of notification shown when Test Harness Mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="test_harness_mode_notification_message">Perform a factory reset to disable Test Harness Mode.</string>
 
+    <!-- Title of notification shown when serial console is enabled. [CHAR LIMIT=NONE] -->
+    <string name="console_running_notification_title">Serial console enabled</string>
+    <!-- Message of notification shown when serial console is enabled. [CHAR LIMIT=NONE] -->
+    <string name="console_running_notification_message">Performance is impacted. To disable, check bootloader.</string>
+
     <!-- Title of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
     <string name="usb_contaminant_detected_title">Liquid or debris in USB port</string>
     <!-- Message of notification shown when contaminant is detected on the USB port. [CHAR LIMIT=NONE] -->
@@ -5015,9 +5025,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 8aa4a8c..3d0a3b3 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" />
@@ -509,6 +511,7 @@
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
+  <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="integer" name="config_safe_media_volume_index" />
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
@@ -2091,6 +2094,8 @@
   <java-symbol type="string" name="adb_active_notification_title" />
   <java-symbol type="string" name="test_harness_mode_notification_title" />
   <java-symbol type="string" name="test_harness_mode_notification_message" />
+  <java-symbol type="string" name="console_running_notification_title" />
+  <java-symbol type="string" name="console_running_notification_message" />
   <java-symbol type="string" name="taking_remote_bugreport_notification_title" />
   <java-symbol type="string" name="share_remote_bugreport_notification_title" />
   <java-symbol type="string" name="sharing_remote_bugreport_notification_title" />
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/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java b/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
deleted file mode 100644
index 8ab9ddb..0000000
--- a/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
+++ /dev/null
@@ -1,140 +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.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 f3a56e2..f7544af 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBuilder.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBuilder.java
@@ -37,8 +37,6 @@
 
     private ArrayList<String> mOptionalLibraries;
 
-    private String mSharedUid;
-
     public static PackageBuilder builder() {
         return new PackageBuilder();
     }
@@ -49,7 +47,6 @@
         pkg.applicationInfo.flags = mFlags;
         pkg.usesLibraries = mRequiredLibraries;
         pkg.usesOptionalLibraries = mOptionalLibraries;
-        pkg.mSharedUserId = mSharedUid;
         return pkg;
     }
 
@@ -58,11 +55,6 @@
         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/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/data/etc/Android.bp b/data/etc/Android.bp
index 4493f3a..befa637 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -22,6 +22,12 @@
 }
 
 prebuilt_etc {
+    name: "preinstalled-packages-platform.xml",
+    sub_dir: "sysconfig",
+    src: "preinstalled-packages-platform.xml",
+}
+
+prebuilt_etc {
     name: "hiddenapi-package-whitelist.xml",
     sub_dir: "sysconfig",
     src: "hiddenapi-package-whitelist.xml",
@@ -133,3 +139,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/platform.xml b/data/etc/platform.xml
index d66930a..dceb243 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -206,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. -->
@@ -225,8 +229,6 @@
     <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/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
new file mode 100644
index 0000000..ccd8b5b
--- /dev/null
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -0,0 +1,90 @@
+<?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.
+-->
+
+<!--
+This XML file declares which system packages should be initially installed for new users based on
+the type of user. All system packages on the device should ideally have an entry in an xml file
+(keys by its manifest name).
+
+Main user-types (every user will be at least one of these types) are:
+  SYSTEM    (user 0)
+  FULL      (any non-profile human user)
+  PROFILE   (profile human user)
+
+Additional optional types are:   GUEST,    RESTRICTED,    MANAGED_PROFILE,    EPHEMERAL,    DEMO
+
+The meaning of each of these user types is delineated by flags in
+frameworks/base/core/java/android/content/pm/UserInfo.java.
+See frameworks/base/services/core/java/com/android/server/pm/UserSystemPackageInstaller#getFlagsFromUserTypes
+
+The following three examples should cover most normal cases:
+
+1. For a system package to be pre-installed only in user 0:
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="SYSTEM">
+   </install-in-user-type>
+
+
+2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be
+installed on any user of type type FULL or PROFILE (since this covers all human users):
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="FULL">
+       <install-in user-type="PROFILE">
+   </install-in-user-type>
+
+
+3. For a system package to be pre-installed on all human users except for profile users (e.g. a
+wallpaper app, since profiles cannot display wallpaper):
+
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="FULL">
+   </install-in-user-type>
+
+
+Some system packages truly are required to be on all users, regardless of type, in which case use:
+   <install-in-user-type package="com.android.example">
+       <install-in user-type="SYSTEM">
+       <install-in user-type="FULL">
+       <install-in user-type="PROFILE">
+   </install-in-user-type>
+
+More fine-grained options are also available (see below). Additionally, packages can blacklist
+user types. Blacklists override any whitelisting (in any file).
+E.g.
+     <install-in-user-type package="com.android.example">
+        <install-in user-type="FULL" />
+        <do-not-install-in user-type="GUEST" />
+    </install-in-user-type>
+
+If a user is of type FULL and GUEST, this package will NOT be installed, because the
+'do-not-install-in' takes precedence over 'install-in'.
+
+The way that a device treats system packages that do not have any entry (for any user type) at all
+is determined by the config resource value config_userTypePackageWhitelistMode.
+See frameworks/base/core/res/res/values/config.xml#config_userTypePackageWhitelistMode.
+
+Changes to the whitelist during system updates can result in installing new system packages
+to pre-existing users, but cannot uninstall system packages from pre-existing users.
+-->
+<config>
+    <install-in-user-type package="com.android.providers.settings">
+        <install-in user-type="SYSTEM" />
+        <install-in user-type="FULL" />
+        <install-in user-type="PROFILE" />
+    </install-in-user-type>
+</config>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
new file mode 100644
index 0000000..f32935f
--- /dev/null
+++ b/data/etc/services.core.protolog.json
@@ -0,0 +1,16 @@
+{
+  "version": "1.0.0",
+  "messages": {
+    "594230385": {
+      "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+      "level": "ERROR",
+      "group": "TEST_GROUP",
+      "at": "com\/android\/server\/wm\/ProtoLogGroup.java:94"
+    }
+  },
+  "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/keystore/java/android/security/keystore/AttestationUtils.java b/keystore/java/android/security/keystore/AttestationUtils.java
index bd497c1..94499ce 100644
--- a/keystore/java/android/security/keystore/AttestationUtils.java
+++ b/keystore/java/android/security/keystore/AttestationUtils.java
@@ -74,6 +74,13 @@
     public static final int ID_TYPE_MEID = 3;
 
     /**
+     * Specifies that the device should attest its MEIDs. For use with {@link #attestDeviceIds}.
+     *
+     * @see #attestDeviceIds
+     */
+    public static final int USE_INDIVIDUAL_ATTESTATION = 4;
+
+    /**
      * Creates an array of X509Certificates from the provided KeymasterCertificateChain.
      *
      * @hide Only called by the DevicePolicyManager.
@@ -196,6 +203,13 @@
                             meid.getBytes(StandardCharsets.UTF_8));
                     break;
                 }
+                case USE_INDIVIDUAL_ATTESTATION: {
+                    //TODO: Add the Keymaster tag for requesting the use of individual
+                    //attestation certificate, which should be
+                    //KeymasterDefs.KM_TAG_DEVICE_UNIQUE_ATTESTATION
+                    attestArgs.addBoolean(720);
+                    break;
+                }
                 default:
                     throw new IllegalArgumentException("Unknown device ID type " + idType);
             }
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 0ad050d..684dc22 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -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,7 +484,7 @@
         swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC);
         swap.vsyncTime = mRenderThread.timeLord().latestVsync();
         if (didDraw) {
-            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.
@@ -493,9 +493,7 @@
                 swap.dequeueDuration =
                         us2ns(ANativeWindow_getLastDequeueDuration(mNativeSurface.get()));
             }
-            int durationUs;
-            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;
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/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
new file mode 100644
index 0000000..c41023e
--- /dev/null
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -0,0 +1,139 @@
+/*
+ * 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 android.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * A base class to manage listeners that have a 1:N -> source:listener relationship.
+ *
+ * @hide
+ */
+abstract class AbstractListenerManager<T> {
+
+    private static class Registration<T> {
+        private final Executor mExecutor;
+        @Nullable private volatile T mListener;
+
+        private Registration(Executor executor, T listener) {
+            Preconditions.checkArgument(listener != null);
+            Preconditions.checkArgument(executor != null);
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        private void unregister() {
+            mListener = null;
+        }
+
+        private void execute(Consumer<T> operation) {
+            mExecutor.execute(() -> {
+                T listener = mListener;
+                if (listener == null) {
+                    return;
+                }
+
+                // we may be under the binder identity if a direct executor is used
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    operation.accept(listener);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            });
+        }
+    }
+
+    @GuardedBy("mListeners")
+    private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>();
+
+    public boolean addListener(@NonNull T listener, @NonNull Handler handler)
+            throws RemoteException {
+        return addInternal(listener, handler);
+    }
+
+    public boolean addListener(@NonNull T listener, @NonNull Executor executor)
+            throws RemoteException {
+        return addInternal(listener, executor);
+    }
+
+    protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
+        return addInternal(listener, new HandlerExecutor(handler));
+    }
+
+    protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
+        return addInternal(listener, new Registration<>(executor, convertKey(listener)));
+    }
+
+    private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
+        Preconditions.checkNotNull(key);
+        Preconditions.checkNotNull(registration);
+
+        synchronized (mListeners) {
+            if (mListeners.isEmpty() && !registerService()) {
+                return false;
+            }
+            Registration<T> oldRegistration = mListeners.put(key, registration);
+            if (oldRegistration != null) {
+                oldRegistration.unregister();
+            }
+            return true;
+        }
+    }
+
+    public void removeListener(Object listener) throws RemoteException {
+        synchronized (mListeners) {
+            Registration<T> oldRegistration = mListeners.remove(listener);
+            if (oldRegistration == null) {
+                return;
+            }
+            oldRegistration.unregister();
+
+            if (mListeners.isEmpty()) {
+                unregisterService();
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected T convertKey(@NonNull Object listener) {
+        return (T) listener;
+    }
+
+    protected abstract boolean registerService() throws RemoteException;
+    protected abstract void unregisterService() throws RemoteException;
+
+    protected void execute(Consumer<T> operation) {
+        synchronized (mListeners) {
+            for (Registration<T> registration : mListeners.values()) {
+                registration.execute(operation);
+            }
+        }
+    }
+}
diff --git a/location/java/android/location/BatchedLocationCallbackTransport.java b/location/java/android/location/BatchedLocationCallbackTransport.java
deleted file mode 100644
index e00f855..0000000
--- a/location/java/android/location/BatchedLocationCallbackTransport.java
+++ /dev/null
@@ -1,66 +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.location;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-import java.util.List;
-
-/**
- * A handler class to manage transport callbacks for {@link BatchedLocationCallback}.
- *
- * @hide
- */
-class BatchedLocationCallbackTransport
-        extends LocalListenerHelper<BatchedLocationCallback> {
-    private final ILocationManager mLocationManager;
-
-    private final IBatchedLocationCallback mCallbackTransport = new CallbackTransport();
-
-    public BatchedLocationCallbackTransport(Context context, ILocationManager locationManager) {
-        super(context, "BatchedLocationCallbackTransport");
-        mLocationManager = locationManager;
-    }
-
-    @Override
-    protected boolean registerWithServer() throws RemoteException {
-        return mLocationManager.addGnssBatchingCallback(
-                mCallbackTransport,
-                getContext().getPackageName());
-    }
-
-    @Override
-    protected void unregisterFromServer() throws RemoteException {
-        mLocationManager.removeGnssBatchingCallback();
-    }
-
-    private class CallbackTransport extends IBatchedLocationCallback.Stub {
-        @Override
-        public void onLocationBatch(final List<Location> locations) {
-            ListenerOperation<BatchedLocationCallback> operation =
-                    new ListenerOperation<BatchedLocationCallback>() {
-                @Override
-                public void execute(BatchedLocationCallback callback)
-                        throws RemoteException {
-                    callback.onLocationBatch(locations);
-                }
-            };
-            foreach(operation);
-        }
-    }
-}
diff --git a/location/java/android/location/GnssMeasurementCallbackTransport.java b/location/java/android/location/GnssMeasurementCallbackTransport.java
deleted file mode 100644
index 8cb8c0b..0000000
--- a/location/java/android/location/GnssMeasurementCallbackTransport.java
+++ /dev/null
@@ -1,97 +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 android.location;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * A handler class to manage transport callbacks for {@link GnssMeasurementsEvent.Callback}.
- *
- * @hide
- */
-class GnssMeasurementCallbackTransport
-        extends LocalListenerHelper<GnssMeasurementsEvent.Callback> {
-    private static final String TAG = "GnssMeasCbTransport";
-    private final ILocationManager mLocationManager;
-
-    private final IGnssMeasurementsListener mListenerTransport = new ListenerTransport();
-
-    public GnssMeasurementCallbackTransport(Context context, ILocationManager locationManager) {
-        super(context, TAG);
-        mLocationManager = locationManager;
-    }
-
-    @Override
-    protected boolean registerWithServer() throws RemoteException {
-        return mLocationManager.addGnssMeasurementsListener(
-                mListenerTransport,
-                getContext().getPackageName());
-    }
-
-    @Override
-    protected void unregisterFromServer() throws RemoteException {
-        mLocationManager.removeGnssMeasurementsListener(mListenerTransport);
-    }
-
-    /**
-     * Injects GNSS measurement corrections into the GNSS chipset.
-     *
-     * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
-     *     measurement corrections to be injected into the GNSS chipset.
-     */
-    protected void injectGnssMeasurementCorrections(
-            GnssMeasurementCorrections measurementCorrections) throws RemoteException {
-        Preconditions.checkNotNull(measurementCorrections);
-        mLocationManager.injectGnssMeasurementCorrections(
-                measurementCorrections, getContext().getPackageName());
-    }
-
-    protected long getGnssCapabilities() throws RemoteException {
-        return mLocationManager.getGnssCapabilities(getContext().getPackageName());
-    }
-
-    private class ListenerTransport extends IGnssMeasurementsListener.Stub {
-        @Override
-        public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
-            ListenerOperation<GnssMeasurementsEvent.Callback> operation =
-                    new ListenerOperation<GnssMeasurementsEvent.Callback>() {
-                        @Override
-                        public void execute(GnssMeasurementsEvent.Callback callback)
-                                throws RemoteException {
-                            callback.onGnssMeasurementsReceived(event);
-                        }
-                    };
-            foreach(operation);
-        }
-
-        @Override
-        public void onStatusChanged(final int status) {
-            ListenerOperation<GnssMeasurementsEvent.Callback> operation =
-                    new ListenerOperation<GnssMeasurementsEvent.Callback>() {
-                @Override
-                public void execute(GnssMeasurementsEvent.Callback callback)
-                        throws RemoteException {
-                    callback.onStatusChanged(status);
-                }
-            };
-            foreach(operation);
-        }
-    }
-}
diff --git a/location/java/android/location/GnssNavigationMessageCallbackTransport.java b/location/java/android/location/GnssNavigationMessageCallbackTransport.java
deleted file mode 100644
index 1eafd02..0000000
--- a/location/java/android/location/GnssNavigationMessageCallbackTransport.java
+++ /dev/null
@@ -1,79 +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 android.location;
-
-import android.content.Context;
-import android.os.RemoteException;
-
-/**
- * A handler class to manage transport callback for {@link GnssNavigationMessage.Callback}.
- *
- * @hide
- */
-class GnssNavigationMessageCallbackTransport
-        extends LocalListenerHelper<GnssNavigationMessage.Callback> {
-    private final ILocationManager mLocationManager;
-
-    private final IGnssNavigationMessageListener mListenerTransport = new ListenerTransport();
-
-    public GnssNavigationMessageCallbackTransport(
-            Context context,
-            ILocationManager locationManager) {
-        super(context, "GnssNavigationMessageCallbackTransport");
-        mLocationManager = locationManager;
-    }
-
-    @Override
-    protected boolean registerWithServer() throws RemoteException {
-        return mLocationManager.addGnssNavigationMessageListener(
-                mListenerTransport,
-                getContext().getPackageName());
-    }
-
-    @Override
-    protected void unregisterFromServer() throws RemoteException {
-        mLocationManager.removeGnssNavigationMessageListener(mListenerTransport);
-    }
-
-    private class ListenerTransport extends IGnssNavigationMessageListener.Stub {
-        @Override
-        public void onGnssNavigationMessageReceived(final GnssNavigationMessage event) {
-            ListenerOperation<GnssNavigationMessage.Callback> operation =
-                    new ListenerOperation<GnssNavigationMessage.Callback>() {
-                @Override
-                public void execute(GnssNavigationMessage.Callback callback)
-                        throws RemoteException {
-                    callback.onGnssNavigationMessageReceived(event);
-                }
-            };
-            foreach(operation);
-        }
-
-        @Override
-        public void onStatusChanged(final int status) {
-            ListenerOperation<GnssNavigationMessage.Callback> operation =
-                    new ListenerOperation<GnssNavigationMessage.Callback>() {
-                @Override
-                public void execute(GnssNavigationMessage.Callback callback)
-                        throws RemoteException {
-                    callback.onStatusChanged(status);
-                }
-            };
-            foreach(operation);
-        }
-    }
-}
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/LocalListenerHelper.java b/location/java/android/location/LocalListenerHelper.java
deleted file mode 100644
index 592d01d..0000000
--- a/location/java/android/location/LocalListenerHelper.java
+++ /dev/null
@@ -1,134 +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 android.location;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A base handler class to manage transport and local listeners.
- *
- * @hide
- */
-abstract class LocalListenerHelper<TListener> {
-    private final HashMap<TListener, Handler> mListeners = new HashMap<>();
-
-    private final String mTag;
-    private final Context mContext;
-
-    protected LocalListenerHelper(Context context, String name) {
-        Preconditions.checkNotNull(name);
-        mContext = context;
-        mTag = name;
-    }
-
-    /**
-     * Adds a {@param listener} to the list of listeners on which callbacks will be executed. The
-     * execution will happen on the {@param handler} thread or alternatively in the callback thread
-     * if a  {@code null} handler value is passed.
-     */
-    public boolean add(@NonNull TListener listener, Handler handler) {
-        Preconditions.checkNotNull(listener);
-        synchronized (mListeners) {
-            // we need to register with the service first, because we need to find out if the
-            // service will actually support the request before we attempt anything
-            if (mListeners.isEmpty()) {
-                boolean registeredWithService;
-                try {
-                    registeredWithService = registerWithServer();
-                } catch (RemoteException e) {
-                    Log.e(mTag, "Error handling first listener.", e);
-                    return false;
-                }
-                if (!registeredWithService) {
-                    Log.e(mTag, "Unable to register listener transport.");
-                    return false;
-                }
-            }
-            if (mListeners.containsKey(listener)) {
-                return true;
-            }
-            mListeners.put(listener, handler);
-            return true;
-        }
-    }
-
-    public void remove(@NonNull TListener listener) {
-        Preconditions.checkNotNull(listener);
-        synchronized (mListeners) {
-            boolean removed = mListeners.containsKey(listener);
-            mListeners.remove(listener);
-            boolean isLastRemoved = removed && mListeners.isEmpty();
-            if (isLastRemoved) {
-                try {
-                    unregisterFromServer();
-                } catch (RemoteException e) {
-                    Log.v(mTag, "Error handling last listener removal", e);
-                }
-            }
-        }
-    }
-
-    protected abstract boolean registerWithServer() throws RemoteException;
-    protected abstract void unregisterFromServer() throws RemoteException;
-
-    protected interface ListenerOperation<TListener> {
-        void execute(TListener listener) throws RemoteException;
-    }
-
-    protected Context getContext() {
-        return mContext;
-    }
-
-    private void executeOperation(ListenerOperation<TListener> operation, TListener listener) {
-        try {
-            operation.execute(listener);
-        } catch (RemoteException e) {
-            Log.e(mTag, "Error in monitored listener.", e);
-            // don't return, give a fair chance to all listeners to receive the event
-        }
-    }
-
-    protected void foreach(final ListenerOperation<TListener> operation) {
-        Collection<Map.Entry<TListener, Handler>> listeners;
-        synchronized (mListeners) {
-            listeners = new ArrayList<>(mListeners.entrySet());
-        }
-        for (final Map.Entry<TListener, Handler> listener : listeners) {
-            if (listener.getValue() == null) {
-                executeOperation(operation, listener.getKey());
-            } else {
-                listener.getValue().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        executeOperation(operation, listener.getKey());
-                    }
-                });
-            }
-        }
-    }
-}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1cc246b..6b47d1d 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -33,13 +34,13 @@
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -47,47 +48,32 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.util.Preconditions;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 
 /**
- * This class provides access to the system location services.  These
- * services allow applications to obtain periodic updates of the
- * device's geographical location, or to fire an application-specified
- * {@link Intent} when the device enters the proximity of a given
- * geographical location.
+ * This class provides access to the system location services. These services allow applications to
+ * obtain periodic updates of the device's geographical location, or to be notified when the device
+ * enters the proximity of a given geographical location.
  *
- * <p class="note">Unless noted, all Location API methods require
- * the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or
- * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permissions.
- * If your application only has the coarse permission then it will not have
- * access to the GPS or passive location providers. Other providers will still
- * return location results, but the update rate will be throttled and the exact
- * location will be obfuscated to a coarse level of accuracy.
+ * <p class="note">Unless noted, all Location API methods require the {@link
+ * android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link
+ * android.Manifest.permission#ACCESS_FINE_LOCATION} permissions. If your application only has the
+ * coarse permission then it will not have access to fine location providers. Other providers will
+ * still return location results, but the exact location will be obfuscated to a coarse level of
+ * accuracy.
  */
+@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
 @SystemService(Context.LOCATION_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_LOCATION)
 public class LocationManager {
-    private static final String TAG = "LocationManager";
 
-    private final Context mContext;
-    @UnsupportedAppUsage
-    private final ILocationManager mService;
-    private final GnssMeasurementCallbackTransport mGnssMeasurementCallbackTransport;
-    private final GnssNavigationMessageCallbackTransport mGnssNavigationMessageCallbackTransport;
-    private final BatchedLocationCallbackTransport mBatchedLocationCallbackTransport;
-    private final ArrayMap<GnssStatus.Callback, GnssStatusListenerTransport> mGnssStatusListeners =
-            new ArrayMap<>();
-    private final ArrayMap<OnNmeaMessageListener, GnssStatusListenerTransport> mGnssNmeaListeners =
-            new ArrayMap<>();
-    private final ArrayMap<GpsStatus.Listener, GnssStatusListenerTransport> mGpsStatusListeners =
-            new ArrayMap<>();
-    // volatile + GnssStatus final-fields pattern to avoid a partially published object
-    private volatile GnssStatus mGnssStatus;
-    private int mTimeToFirstFix;
+    private static final String TAG = "LocationManager";
 
     /**
      * Name of the network location provider.
@@ -238,112 +224,31 @@
     public static final String METADATA_SETTINGS_FOOTER_STRING =
             "com.android.settings.location.FOOTER_STRING";
 
-    // Map from LocationListeners to their associated ListenerTransport objects
-    private final ArrayMap<LocationListener, ListenerTransport> mListeners = new ArrayMap<>();
+    private final Context mContext;
 
-    private class ListenerTransport extends ILocationListener.Stub {
-        private static final int TYPE_LOCATION_CHANGED = 1;
-        private static final int TYPE_STATUS_CHANGED = 2;
-        private static final int TYPE_PROVIDER_ENABLED = 3;
-        private static final int TYPE_PROVIDER_DISABLED = 4;
+    @UnsupportedAppUsage
+    private final ILocationManager mService;
 
-        private LocationListener mListener;
-        private final Handler mListenerHandler;
+    @GuardedBy("mListeners")
+    private final ArrayMap<LocationListener, LocationListenerTransport> mListeners =
+            new ArrayMap<>();
 
-        ListenerTransport(LocationListener listener, Looper looper) {
-            mListener = listener;
+    @GuardedBy("mBatchedLocationCallbackManager")
+    private final BatchedLocationCallbackManager mBatchedLocationCallbackManager =
+            new BatchedLocationCallbackManager();
+    private final GnssStatusListenerManager
+            mGnssStatusListenerManager = new GnssStatusListenerManager();
+    private final GnssMeasurementsListenerManager mGnssMeasurementsListenerManager =
+            new GnssMeasurementsListenerManager();
+    private final GnssNavigationMessageListenerManager mGnssNavigationMessageListenerTransport =
+            new GnssNavigationMessageListenerManager();
 
-            if (looper == null) {
-                mListenerHandler = new Handler() {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        _handleMessage(msg);
-                    }
-                };
-            } else {
-                mListenerHandler = new Handler(looper) {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        _handleMessage(msg);
-                    }
-                };
-            }
-        }
-
-        @Override
-        public void onLocationChanged(Location location) {
-            Message msg = Message.obtain();
-            msg.what = TYPE_LOCATION_CHANGED;
-            msg.obj = location;
-            sendCallbackMessage(msg);
-        }
-
-        @Override
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-            Message msg = Message.obtain();
-            msg.what = TYPE_STATUS_CHANGED;
-            Bundle b = new Bundle();
-            b.putString("provider", provider);
-            b.putInt("status", status);
-            if (extras != null) {
-                b.putBundle("extras", extras);
-            }
-            msg.obj = b;
-            sendCallbackMessage(msg);
-        }
-
-        @Override
-        public void onProviderEnabled(String provider) {
-            Message msg = Message.obtain();
-            msg.what = TYPE_PROVIDER_ENABLED;
-            msg.obj = provider;
-            sendCallbackMessage(msg);
-        }
-
-        @Override
-        public void onProviderDisabled(String provider) {
-            Message msg = Message.obtain();
-            msg.what = TYPE_PROVIDER_DISABLED;
-            msg.obj = provider;
-            sendCallbackMessage(msg);
-        }
-
-        private void sendCallbackMessage(Message msg) {
-            if (!mListenerHandler.sendMessage(msg)) {
-                locationCallbackFinished();
-            }
-        }
-
-        private void _handleMessage(Message msg) {
-            switch (msg.what) {
-                case TYPE_LOCATION_CHANGED:
-                    Location location = new Location((Location) msg.obj);
-                    mListener.onLocationChanged(location);
-                    break;
-                case TYPE_STATUS_CHANGED:
-                    Bundle b = (Bundle) msg.obj;
-                    String provider = b.getString("provider");
-                    int status = b.getInt("status");
-                    Bundle extras = b.getBundle("extras");
-                    mListener.onStatusChanged(provider, status, extras);
-                    break;
-                case TYPE_PROVIDER_ENABLED:
-                    mListener.onProviderEnabled((String) msg.obj);
-                    break;
-                case TYPE_PROVIDER_DISABLED:
-                    mListener.onProviderDisabled((String) msg.obj);
-                    break;
-            }
-            locationCallbackFinished();
-        }
-
-        private void locationCallbackFinished() {
-            try {
-                mService.locationCallbackFinished(this);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
+    /**
+     * @hide
+     */
+    public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
+        mService = service;
+        mContext = context;
     }
 
     /**
@@ -371,858 +276,98 @@
     }
 
     /**
-     * @hide - hide this constructor because it has a parameter
-     * of type ILocationManager, which is a system private class. The
-     * right way to create an instance of this class is using the
-     * factory Context.getSystemService.
-     */
-    public LocationManager(@NonNull Context context, @NonNull ILocationManager service) {
-        mService = service;
-        mContext = context;
-        mGnssMeasurementCallbackTransport =
-                new GnssMeasurementCallbackTransport(mContext, mService);
-        mGnssNavigationMessageCallbackTransport =
-                new GnssNavigationMessageCallbackTransport(mContext, mService);
-        mBatchedLocationCallbackTransport =
-                new BatchedLocationCallbackTransport(mContext, mService);
-
-    }
-
-    private LocationProvider createProvider(String name, ProviderProperties properties) {
-        return new LocationProvider(name, properties);
-    }
-
-    /**
-     * Returns a list of the names of all known location providers.
-     * <p>All providers are returned, including ones that are not permitted to
-     * be accessed by the calling activity or are currently disabled.
-     *
-     * @return list of Strings containing names of the provider
-     */
-    public @NonNull List<String> getAllProviders() {
-        try {
-            return mService.getAllProviders();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns a list of the names of location providers.
-     *
-     * @param enabledOnly if true then only the providers which are currently
-     * enabled are returned.
-     * @return list of Strings containing names of the providers
-     */
-    public @NonNull List<String> getProviders(boolean enabledOnly) {
-        try {
-            return mService.getProviders(null, enabledOnly);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the information associated with the location provider of the
-     * given name, or null if no provider exists by that name.
-     *
-     * @param name the provider name
-     * @return a LocationProvider, or null
-     *
-     * @throws IllegalArgumentException if name is null or does not exist
-     * @throws SecurityException if the caller is not permitted to access the
-     * given provider.
-     */
-    public @Nullable LocationProvider getProvider(@NonNull String name) {
-        checkProvider(name);
-        try {
-            ProviderProperties properties = mService.getProviderProperties(name);
-            if (properties == null) {
-                return null;
-            }
-            return createProvider(name, properties);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns a list of the names of LocationProviders that satisfy the given
-     * criteria, or null if none do.  Only providers that are permitted to be
-     * accessed by the calling activity will be returned.
-     *
-     * @param criteria the criteria that the returned providers must match
-     * @param enabledOnly if true then only the providers which are currently
-     * enabled are returned.
-     * @return list of Strings containing names of the providers
-     */
-    public @NonNull List<String> getProviders(@NonNull Criteria criteria, boolean enabledOnly) {
-        checkCriteria(criteria);
-        try {
-            return mService.getProviders(criteria, enabledOnly);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the name of the provider that best meets the given criteria. Only providers
-     * that are permitted to be accessed by the calling activity will be
-     * returned.  If several providers meet the criteria, the one with the best
-     * accuracy is returned.  If no provider meets the criteria,
-     * the criteria are loosened in the following sequence:
-     *
-     * <ul>
-     * <li> power requirement
-     * <li> accuracy
-     * <li> bearing
-     * <li> speed
-     * <li> altitude
-     * </ul>
-     *
-     * <p> Note that the requirement on monetary cost is not removed
-     * in this process.
-     *
-     * @param criteria the criteria that need to be matched
-     * @param enabledOnly if true then only a provider that is currently enabled is returned
-     * @return name of the provider that best matches the requirements
-     */
-    public @Nullable String getBestProvider(@NonNull Criteria criteria, boolean enabledOnly) {
-        checkCriteria(criteria);
-        try {
-            return mService.getBestProvider(criteria, enabledOnly);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Register for location updates using the named provider, and a
-     * pending intent.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param provider the name of the provider with which to register
-     * @param minTime minimum time interval between location updates, in milliseconds
-     * @param minDistance minimum distance between location updates, in meters
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called for
-     * each location update
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * on this device
-     * @throws IllegalArgumentException if listener is null
-     * @throws RuntimeException if the calling thread has no Looper
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
-            @NonNull LocationListener listener) {
-        checkProvider(provider);
-        checkListener(listener);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                provider, minTime, minDistance, false);
-        requestLocationUpdates(request, listener, null, null);
-    }
-
-    /**
-     * Register for location updates using the named provider, and a callback on
-     * the specified looper thread.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param provider the name of the provider with which to register
-     * @param minTime minimum time interval between location updates, in milliseconds
-     * @param minDistance minimum distance between location updates, in meters
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called for
-     * each location update
-     * @param looper a Looper object whose message queue will be used to
-     * implement the callback mechanism, or null to make callbacks on the calling
-     * thread
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * @throws IllegalArgumentException if listener is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
-            @NonNull LocationListener listener, @Nullable Looper looper) {
-        checkProvider(provider);
-        checkListener(listener);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                provider, minTime, minDistance, false);
-        requestLocationUpdates(request, listener, looper, null);
-    }
-
-    /**
-     * Register for location updates using a Criteria, and a callback
-     * on the specified looper thread.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param minTime minimum time interval between location updates, in milliseconds
-     * @param minDistance minimum distance between location updates, in meters
-     * @param criteria contains parameters for the location manager to choose the
-     * appropriate provider and parameters to compute the location
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called for
-     * each location update
-     * @param looper a Looper object whose message queue will be used to
-     * implement the callback mechanism, or null to make callbacks on the calling
-     * thread
-     *
-     * @throws IllegalArgumentException if criteria is null
-     * @throws IllegalArgumentException if listener is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(long minTime, float minDistance, @NonNull Criteria criteria,
-            @NonNull LocationListener listener, @Nullable Looper looper) {
-        checkCriteria(criteria);
-        checkListener(listener);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
-                criteria, minTime, minDistance, false);
-        requestLocationUpdates(request, listener, looper, null);
-    }
-
-    /**
-     * Register for location updates using the named provider, and a
-     * pending intent.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param provider the name of the provider with which to register
-     * @param minTime minimum time interval between location updates, in milliseconds
-     * @param minDistance minimum distance between location updates, in meters
-     * @param intent a {@link PendingIntent} to be sent for each location update
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * on this device
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
-            @NonNull PendingIntent intent) {
-        checkProvider(provider);
-        checkPendingIntent(intent);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                provider, minTime, minDistance, false);
-        requestLocationUpdates(request, null, null, intent);
-    }
-
-    /**
-     * Register for location updates using a Criteria and pending intent.
-     *
-     * <p>The <code>requestLocationUpdates()</code> and
-     * <code>requestSingleUpdate()</code> register the current activity to be
-     * updated periodically by the named provider, or by the provider matching
-     * the specified {@link Criteria}, with location and status updates.
-     *
-     * <p> It may take a while to receive the first location update. If
-     * an immediate location is required, applications may use the
-     * {@link #getLastKnownLocation(String)} method.
-     *
-     * <p> Location updates are received either by {@link LocationListener}
-     * callbacks, or by broadcast intents to a supplied {@link PendingIntent}.
-     *
-     * <p> If the caller supplied a pending intent, then location updates
-     * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a
-     * {@link android.location.Location} value.
-     *
-     * <p> The location update interval can be controlled using the minTime parameter.
-     * The elapsed time between location updates will never be less than
-     * minTime, although it can be more depending on the Location Provider
-     * implementation and the update interval requested by other applications.
-     *
-     * <p> Choosing a sensible value for minTime is important to conserve
-     * battery life. Each location update requires power from
-     * GPS, WIFI, Cell and other radios. Select a minTime value as high as
-     * possible while still providing a reasonable user experience.
-     * If your application is not in the foreground and showing
-     * location to the user then your application should avoid using an active
-     * provider (such as {@link #NETWORK_PROVIDER} or {@link #GPS_PROVIDER}),
-     * but if you insist then select a minTime of 5 * 60 * 1000 (5 minutes)
-     * or greater. If your application is in the foreground and showing
-     * location to the user then it is appropriate to select a faster
-     * update interval.
-     *
-     * <p> The minDistance parameter can also be used to control the
-     * frequency of location updates. If it is greater than 0 then the
-     * location provider will only send your application an update when
-     * the location has changed by at least minDistance meters, AND
-     * at least minTime milliseconds have passed. However it is more
-     * difficult for location providers to save power using the minDistance
-     * parameter, so minTime should be the primary tool to conserving battery
-     * life.
-     *
-     * <p> If your application wants to passively observe location
-     * updates triggered by other applications, but not consume
-     * any additional power otherwise, then use the {@link #PASSIVE_PROVIDER}
-     * This provider does not actively turn on or modify active location
-     * providers, so you do not need to be as careful about minTime and
-     * minDistance. However if your application performs heavy work
-     * on a location update (such as network activity) then you should
-     * select non-zero values for minTime and/or minDistance to rate-limit
-     * your update frequency in the case another application enables a
-     * location provider with extremely fast updates.
-     *
-     * <p>In case the provider is disabled by the user, updates will stop,
-     * and a provider availability update will be sent.
-     * As soon as the provider is enabled again,
-     * location updates will immediately resume and a provider availability
-     * update sent. Providers can also send status updates, at any time,
-     * with extra's specific to the provider. If a callback was supplied
-     * then status and availability updates are via
-     * {@link LocationListener#onProviderDisabled},
-     * {@link LocationListener#onProviderEnabled} or
-     * {@link LocationListener#onStatusChanged}. Alternately, if a
-     * pending intent was supplied then status and availability updates
-     * are broadcast intents with extra keys of
-     * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED}.
-     *
-     * <p> If a {@link LocationListener} is used but with no Looper specified
-     * then the calling thread must already
-     * be a {@link android.os.Looper} thread such as the main thread of the
-     * calling Activity. If a Looper is specified with a {@link LocationListener}
-     * then callbacks are made on the supplied Looper thread.
-     *
-     * <p> When location callbacks are invoked, the system will hold a wakelock
-     * on your application's behalf for some period of time, but not
-     * indefinitely. If your application requires a long running wakelock
-     * within the location callback, you should acquire it yourself.
-     *
-     * <p class="note"> Prior to Jellybean, the minTime parameter was
-     * only a hint, and some location provider implementations ignored it.
-     * From Jellybean and onwards it is mandatory for Android compatible
-     * devices to observe both the minTime and minDistance parameters.
-     *
-     * @param minTime minimum time interval between location updates, in milliseconds
-     * @param minDistance minimum distance between location updates, in meters
-     * @param criteria contains parameters for the location manager to choose the
-     * appropriate provider and parameters to compute the location
-     * @param intent a {@link PendingIntent} to be sent for each location update
-     *
-     * @throws IllegalArgumentException if criteria is null
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(long minTime, float minDistance, @NonNull Criteria criteria,
-            @NonNull PendingIntent intent) {
-        checkCriteria(criteria);
-        checkPendingIntent(intent);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
-                criteria, minTime, minDistance, false);
-        requestLocationUpdates(request, null, null, intent);
-    }
-
-    /**
-     * Register for a single location update using the named provider and
-     * a callback.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param provider the name of the provider with which to register
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called when
-     * the location update is available
-     * @param looper a Looper object whose message queue will be used to
-     * implement the callback mechanism, or null to make callbacks on the calling
-     * thread
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * @throws IllegalArgumentException if listener is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestSingleUpdate(
-            @NonNull String provider, @NonNull LocationListener listener, @Nullable Looper looper) {
-        checkProvider(provider);
-        checkListener(listener);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                provider, 0, 0, true);
-        requestLocationUpdates(request, listener, looper, null);
-    }
-
-    /**
-     * Register for a single location update using a Criteria and
-     * a callback.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param criteria contains parameters for the location manager to choose the
-     * appropriate provider and parameters to compute the location
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called when
-     * the location update is available
-     * @param looper a Looper object whose message queue will be used to
-     * implement the callback mechanism, or null to make callbacks on the calling
-     * thread
-     *
-     * @throws IllegalArgumentException if criteria is null
-     * @throws IllegalArgumentException if listener is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestSingleUpdate(
-            @NonNull Criteria criteria,
-            @NonNull LocationListener listener,
-            @Nullable Looper looper) {
-        checkCriteria(criteria);
-        checkListener(listener);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
-                criteria, 0, 0, true);
-        requestLocationUpdates(request, listener, looper, null);
-    }
-
-    /**
-     * Register for a single location update using a named provider and pending intent.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param provider the name of the provider with which to register
-     * @param intent a {@link PendingIntent} to be sent for the location update
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestSingleUpdate(@NonNull String provider, @NonNull PendingIntent intent) {
-        checkProvider(provider);
-        checkPendingIntent(intent);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
-                provider, 0, 0, true);
-        requestLocationUpdates(request, null, null, intent);
-    }
-
-    /**
-     * Register for a single location update using a Criteria and pending intent.
-     *
-     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
-     * for more detail on how to use this method.
-     *
-     * @param criteria contains parameters for the location manager to choose the
-     * appropriate provider and parameters to compute the location
-     * @param intent a {@link PendingIntent} to be sent for the location update
-     *
-     * @throws IllegalArgumentException if provider is null or doesn't exist
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if no suitable permission is present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestSingleUpdate(@NonNull Criteria criteria, @NonNull PendingIntent intent) {
-        checkCriteria(criteria);
-        checkPendingIntent(intent);
-
-        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
-                criteria, 0, 0, true);
-        requestLocationUpdates(request, null, null, intent);
-    }
-
-    /**
-     * Register for fused location updates using a LocationRequest and callback.
-     *
-     * <p>Upon a location update, the system delivers the new {@link Location} to the
-     * provided {@link LocationListener}, by calling its {@link
-     * LocationListener#onLocationChanged} method.</p>
-     *
-     * <p>The system will automatically select and enable the best providers
-     * to compute a location for your application. It may use only passive
-     * locations, or just a single location source, or it may fuse together
-     * multiple location sources in order to produce the best possible
-     * result, depending on the quality of service requested in the
-     * {@link LocationRequest}.
-     *
-     * <p>LocationRequest can be null, in which case the system will choose
-     * default, low power parameters for location updates. You will occasionally
-     * receive location updates as available, without a major power impact on the
-     * system. If your application just needs an occasional location update
-     * without any strict demands, then pass a null LocationRequest.
-     *
-     * <p>Only one LocationRequest can be registered for each unique callback
-     * or pending intent. So a subsequent request with the same callback or
-     * pending intent will over-write the previous LocationRequest.
-     *
-     * <p> If a pending intent is supplied then location updates
-     * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a
-     * {@link android.location.Location} value. If a callback is supplied
-     * then location updates are made using the
-     * {@link LocationListener#onLocationChanged} callback, on the specified
-     * Looper thread. If a {@link LocationListener} is used
-     * but with a null Looper then the calling thread must already
-     * be a {@link android.os.Looper} thread (such as the main thread) and
-     * callbacks will occur on this thread.
-     *
-     * <p> Provider status updates and availability updates are deprecated
-     * because the system is performing provider fusion on the applications
-     * behalf. So {@link LocationListener#onProviderDisabled},
-     * {@link LocationListener#onProviderEnabled}, {@link LocationListener#onStatusChanged}
-     * will not be called, and intents with extra keys of
-     * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED} will not
-     * be received.
-     *
-     * <p> To unregister for Location updates, use: {@link #removeUpdates(LocationListener)}.
-     *
-     * @param request quality of service required, null for default low power
-     * @param listener a {@link LocationListener} whose
-     * {@link LocationListener#onLocationChanged} method will be called when
-     * the location update is available
-     * @param looper a Looper object whose message queue will be used to
-     * implement the callback mechanism, or null to make callbacks on the calling
-     * thread
-     *
-     * @throws IllegalArgumentException if listener is null
-     * @throws SecurityException if no suitable permission is present
+     * Returns the extra location controller package on the device.
      *
      * @hide
      */
     @SystemApi
-    @TestApi
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(
-            @NonNull LocationRequest request,
-            @NonNull LocationListener listener,
-            @Nullable Looper looper) {
-        checkListener(listener);
-        requestLocationUpdates(request, listener, looper, null);
+    public @Nullable String getExtraLocationControllerPackage() {
+        try {
+            return mService.getExtraLocationControllerPackage();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return null;
+        }
     }
 
-
     /**
-     * Register for fused location updates using a LocationRequest and a pending intent.
-     *
-     * <p>Upon a location update, the system delivers the new {@link Location} with your provided
-     * {@link PendingIntent}, as the value for {@link LocationManager#KEY_LOCATION_CHANGED}
-     * in the intent's extras.</p>
-     *
-     * <p> To unregister for Location updates, use: {@link #removeUpdates(PendingIntent)}.
-     *
-     * <p> See {@link #requestLocationUpdates(LocationRequest, LocationListener, Looper)}
-     * for more detail.
-     *
-     * @param request quality of service required, null for default low power
-     * @param intent a {@link PendingIntent} to be sent for the location update
-     *
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if no suitable permission is present
+     * Set the extra location controller package for location services on the device.
      *
      * @hide
      */
     @SystemApi
-    @TestApi
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void requestLocationUpdates(
-            @NonNull LocationRequest request, @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        requestLocationUpdates(request, null, null, intent);
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setExtraLocationControllerPackage(@Nullable String packageName) {
+        try {
+            mService.setExtraLocationControllerPackage(packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     /**
-     * Set the last known location with a new location.
-     *
-     * <p>A privileged client can inject a {@link Location} if it has a better estimate of what
-     * the recent location is.  This is especially useful when the device boots up and the GPS
-     * chipset is in the process of getting the first fix.  If the client has cached the location,
-     * it can inject the {@link Location}, so if an app requests for a {@link Location} from {@link
-     * #getLastKnownLocation(String)}, the location information is still useful before getting
-     * the first fix.</p>
-     *
-     * <p> Useful in products like Auto.
-     *
-     * @param newLocation newly available {@link Location} object
-     * @return true if update was successful, false if not
-     *
-     * @throws SecurityException if no suitable permission is present
+     * Set whether the extra location controller package is currently enabled on the device.
      *
      * @hide
      */
-    @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION})
-    public boolean injectLocation(@NonNull Location newLocation) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setExtraLocationControllerPackageEnabled(boolean enabled) {
         try {
-            return mService.injectLocation(newLocation);
+            mService.setExtraLocationControllerPackageEnabled(enabled);
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private ListenerTransport wrapListener(LocationListener listener, Looper looper) {
-        if (listener == null) return null;
-        synchronized (mListeners) {
-            ListenerTransport transport = mListeners.get(listener);
-            if (transport == null) {
-                transport = new ListenerTransport(listener, looper);
-            }
-            mListeners.put(listener, transport);
-            return transport;
-        }
-    }
-
-    @UnsupportedAppUsage
-    private void requestLocationUpdates(LocationRequest request, LocationListener listener,
-            Looper looper, PendingIntent intent) {
-
-        String packageName = mContext.getPackageName();
-
-        // wrap the listener class
-        ListenerTransport transport = wrapListener(listener, looper);
-
-        try {
-            mService.requestLocationUpdates(request, transport, intent, packageName);
-       } catch (RemoteException e) {
-           throw e.rethrowFromSystemServer();
-       }
-    }
-
-    /**
-     * Removes all location updates for the specified LocationListener.
-     *
-     * <p>Following this call, updates will no longer
-     * occur for this listener.
-     *
-     * @param listener listener object that no longer needs location updates
-     * @throws IllegalArgumentException if listener is null
-     */
-    public void removeUpdates(@NonNull LocationListener listener) {
-        checkListener(listener);
-        String packageName = mContext.getPackageName();
-
-        ListenerTransport transport;
-        synchronized (mListeners) {
-            transport = mListeners.remove(listener);
-        }
-        if (transport == null) return;
-
-        try {
-            mService.removeUpdates(transport, null, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Removes all location updates for the specified pending intent.
-     *
-     * <p>Following this call, updates will no longer for this pending intent.
-     *
-     * @param intent pending intent object that no longer needs location updates
-     * @throws IllegalArgumentException if intent is null
-     */
-    public void removeUpdates(@NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        String packageName = mContext.getPackageName();
-
-        try {
-            mService.removeUpdates(null, intent, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Set a proximity alert for the location given by the position
-     * (latitude, longitude) and the given radius.
-     *
-     * <p> When the device
-     * detects that it has entered or exited the area surrounding the
-     * location, the given PendingIntent will be used to create an Intent
-     * to be fired.
-     *
-     * <p> The fired Intent will have a boolean extra added with key
-     * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is
-     * entering the proximity region; if false, it is exiting.
-     *
-     * <p> Due to the approximate nature of position estimation, if the
-     * device passes through the given area briefly, it is possible
-     * that no Intent will be fired.  Similarly, an Intent could be
-     * fired if the device passes very close to the given area but
-     * does not actually enter it.
-     *
-     * <p> After the number of milliseconds given by the expiration
-     * parameter, the location manager will delete this proximity
-     * alert and no longer monitor it.  A value of -1 indicates that
-     * there should be no expiration time.
-     *
-     * <p> Internally, this method uses both {@link #NETWORK_PROVIDER}
-     * and {@link #GPS_PROVIDER}.
-     *
-     * <p>Before API version 17, this method could be used with
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
-     * From API version 17 and onwards, this method requires
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission.
-     *
-     * @param latitude the latitude of the central point of the
-     * alert region
-     * @param longitude the longitude of the central point of the
-     * alert region
-     * @param radius the radius of the central point of the
-     * alert region, in meters
-     * @param expiration time for this proximity alert, in milliseconds,
-     * or -1 to indicate no expiration
-     * @param intent a PendingIntent that will be used to generate an Intent to
-     * fire when entry to or exit from the alert region is detected
-     *
-     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
-     * permission is not present
-     */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void addProximityAlert(double latitude, double longitude, float radius, long expiration,
-            @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        if (expiration < 0) expiration = Long.MAX_VALUE;
-
-        Geofence fence = Geofence.createCircle(latitude, longitude, radius);
-        LocationRequest request = new LocationRequest().setExpireIn(expiration);
-        try {
-            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Add a geofence with the specified LocationRequest quality of service.
-     *
-     * <p> When the device
-     * detects that it has entered or exited the area surrounding the
-     * location, the given PendingIntent will be used to create an Intent
-     * to be fired.
-     *
-     * <p> The fired Intent will have a boolean extra added with key
-     * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is
-     * entering the proximity region; if false, it is exiting.
-     *
-     * <p> The geofence engine fuses results from all location providers to
-     * provide the best balance between accuracy and power. Applications
-     * can choose the quality of service required using the
-     * {@link LocationRequest} object. If it is null then a default,
-     * low power geo-fencing implementation is used. It is possible to cross
-     * a geo-fence without notification, but the system will do its best
-     * to detect, using {@link LocationRequest} as a hint to trade-off
-     * accuracy and power.
-     *
-     * <p> The power required by the geofence engine can depend on many factors,
-     * such as quality and interval requested in {@link LocationRequest},
-     * distance to nearest geofence and current device velocity.
-     *
-     * @param request quality of service required, null for default low power
-     * @param fence a geographical description of the geofence area
-     * @param intent pending intent to receive geofence updates
-     *
-     * @throws IllegalArgumentException if fence is null
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
-     * permission is not present
+     * Returns whether extra location controller package is currently enabled on the device.
      *
      * @hide
      */
-    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
-    public void addGeofence(
-            @NonNull LocationRequest request,
-            @NonNull Geofence fence,
-            @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        checkGeofence(fence);
-
+    @SystemApi
+    public boolean isExtraLocationControllerPackageEnabled() {
         try {
-            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+            return mService.isExtraLocationControllerPackageEnabled();
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            e.rethrowFromSystemServer();
+            return false;
         }
     }
 
     /**
-     * Removes the proximity alert with the given PendingIntent.
+     * Set the extra location controller package for location services on the device.
      *
-     * <p>Before API version 17, this method could be used with
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
-     * From API version 17 and onwards, this method requires
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission.
-     *
-     * @param intent the PendingIntent that no longer needs to be notified of
-     * proximity alerts
-     *
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
-     * permission is not present
-     */
-    public void removeProximityAlert(@NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        String packageName = mContext.getPackageName();
-
-        try {
-            mService.removeGeofence(null, intent, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Remove a single geofence.
-     *
-     * <p>This removes only the specified geofence associated with the
-     * specified pending intent. All other geofences remain unchanged.
-     *
-     * @param fence a geofence previously passed to {@link #addGeofence}
-     * @param intent a pending intent previously passed to {@link #addGeofence}
-     *
-     * @throws IllegalArgumentException if fence is null
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
-     * permission is not present
-     *
+     * @removed
+     * @deprecated Use {@link #setExtraLocationControllerPackage} instead.
      * @hide
      */
-    public void removeGeofence(@NonNull Geofence fence, @NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        checkGeofence(fence);
-        String packageName = mContext.getPackageName();
-
+    @Deprecated
+    @SystemApi
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setLocationControllerExtraPackage(String packageName) {
         try {
-            mService.removeGeofence(fence, intent, packageName);
+            mService.setExtraLocationControllerPackage(packageName);
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Remove all geofences registered to the specified pending intent.
+     * Set whether the extra location controller package is currently enabled on the device.
      *
-     * @param intent a pending intent previously passed to {@link #addGeofence}
-     *
-     * @throws IllegalArgumentException if intent is null
-     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
-     * permission is not present
-     *
+     * @removed
+     * @deprecated Use {@link #setExtraLocationControllerPackageEnabled} instead.
      * @hide
      */
-    public void removeAllGeofences(@NonNull PendingIntent intent) {
-        checkPendingIntent(intent);
-        String packageName = mContext.getPackageName();
-
+    @SystemApi
+    @Deprecated
+    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
+    public void setLocationControllerExtraPackageEnabled(boolean enabled) {
         try {
-            mService.removeGeofence(null, intent, packageName);
+            mService.setExtraLocationControllerPackageEnabled(enabled);
         } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+            e.rethrowFromSystemServer();
         }
     }
 
@@ -1406,6 +551,765 @@
     }
 
     /**
+     * Register for a single location update using the named provider and
+     * a callback.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param provider the name of the provider with which to register
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called when
+     * the location update is available
+     * @param looper a Looper object whose message queue will be used to
+     * implement the callback mechanism, or null to make callbacks on the calling
+     * thread
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * @throws IllegalArgumentException if listener is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestSingleUpdate(
+            @NonNull String provider, @NonNull LocationListener listener, @Nullable Looper looper) {
+        checkProvider(provider);
+        checkListener(listener);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, 0, 0, true);
+        requestLocationUpdates(request, listener, looper);
+    }
+
+    /**
+     * Register for a single location update using a Criteria and
+     * a callback.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param criteria contains parameters for the location manager to choose the
+     * appropriate provider and parameters to compute the location
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called when
+     * the location update is available
+     * @param looper a Looper object whose message queue will be used to
+     * implement the callback mechanism, or null to make callbacks on the calling
+     * thread
+     *
+     * @throws IllegalArgumentException if criteria is null
+     * @throws IllegalArgumentException if listener is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestSingleUpdate(
+            @NonNull Criteria criteria,
+            @NonNull LocationListener listener,
+            @Nullable Looper looper) {
+        checkCriteria(criteria);
+        checkListener(listener);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+                criteria, 0, 0, true);
+        requestLocationUpdates(request, listener, looper);
+    }
+
+    /**
+     * Register for a single location update using a named provider and pending intent.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param provider the name of the provider with which to register
+     * @param intent a {@link PendingIntent} to be sent for the location update
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestSingleUpdate(@NonNull String provider, @NonNull PendingIntent intent) {
+        checkProvider(provider);
+        checkPendingIntent(intent);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, 0, 0, true);
+        requestLocationUpdates(request, intent);
+    }
+
+    /**
+     * Register for a single location update using a Criteria and pending intent.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param criteria contains parameters for the location manager to choose the
+     * appropriate provider and parameters to compute the location
+     * @param intent a {@link PendingIntent} to be sent for the location update
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestSingleUpdate(@NonNull Criteria criteria, @NonNull PendingIntent intent) {
+        checkCriteria(criteria);
+        checkPendingIntent(intent);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+                criteria, 0, 0, true);
+        requestLocationUpdates(request, intent);
+    }
+
+    /**
+     * Register for location updates using the named provider, and a
+     * pending intent.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param provider the name of the provider with which to register
+     * @param minTime minimum time interval between location updates, in milliseconds
+     * @param minDistance minimum distance between location updates, in meters
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called for
+     * each location update
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * on this device
+     * @throws IllegalArgumentException if listener is null
+     * @throws RuntimeException if the calling thread has no Looper
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
+            @NonNull LocationListener listener) {
+        checkProvider(provider);
+        checkListener(listener);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, minTime, minDistance, false);
+        requestLocationUpdates(request, listener, null);
+    }
+
+    /**
+     * Register for location updates using the named provider, and a callback on
+     * the specified looper thread.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param provider the name of the provider with which to register
+     * @param minTime minimum time interval between location updates, in milliseconds
+     * @param minDistance minimum distance between location updates, in meters
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called for
+     * each location update
+     * @param looper a Looper object whose message queue will be used to
+     * implement the callback mechanism, or null to make callbacks on the calling
+     * thread
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * @throws IllegalArgumentException if listener is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
+            @NonNull LocationListener listener, @Nullable Looper looper) {
+        checkProvider(provider);
+        checkListener(listener);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, minTime, minDistance, false);
+        requestLocationUpdates(request, listener, looper);
+    }
+
+    /**
+     * Register for location updates from the given provider with the given arguments. {@link
+     * LocationListener} callbacks will take place on the given {@link Executor}. Only one request
+     * can be registered for each unique listener, so any subsequent requests with the same listener
+     * will overwrite all associated arguments.
+     *
+     * <p>See {@link #requestLocationUpdates(String, long, float, LocationListener, Looper)} for
+     * more information.
+     *
+     * @param provider     the name of the provider used for location updates
+     * @param minTimeMs    minimum time interval between location updates, in milliseconds
+     * @param minDistanceM minimum distance between location updates, in meters
+     * @param executor     all listener updates will take place on this {@link Executor}
+     * @param listener     a {@link LocationListener} that will be called when updates are available
+     * @throws IllegalArgumentException if provider, listener, or looper is null or nonexistant
+     * @throws SecurityException        if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(
+            @NonNull String provider,
+            long minTimeMs,
+            float minDistanceM,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull LocationListener listener) {
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, minTimeMs, minDistanceM, false);
+        requestLocationUpdates(request, executor, listener);
+    }
+
+    /**
+     * Register for location updates using a Criteria, and a callback
+     * on the specified looper thread.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param minTime minimum time interval between location updates, in milliseconds
+     * @param minDistance minimum distance between location updates, in meters
+     * @param criteria contains parameters for the location manager to choose the
+     * appropriate provider and parameters to compute the location
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called for
+     * each location update
+     * @param looper a Looper object whose message queue will be used to
+     * implement the callback mechanism, or null to make callbacks on the calling
+     * thread
+     *
+     * @throws IllegalArgumentException if criteria is null
+     * @throws IllegalArgumentException if listener is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(long minTime, float minDistance, @NonNull Criteria criteria,
+            @NonNull LocationListener listener, @Nullable Looper looper) {
+        checkCriteria(criteria);
+        checkListener(listener);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+                criteria, minTime, minDistance, false);
+        requestLocationUpdates(request, listener, looper);
+    }
+
+    /**
+     * Uses the given {@link Criteria} to select a single provider to use for location updates.
+     *
+     * <p>See {@link #requestLocationUpdates(String, long, float, Executor, LocationListener)} for
+     * more information.
+     *
+     * @param minTimeMs    minimum time interval between location updates, in milliseconds
+     * @param minDistanceM minimum distance between location updates, in meters
+     * @param criteria     the {@link Criteria} used to select a provider for location updates
+     * @param executor     all listener updates will take place on this {@link Executor}
+     * @param listener     a {@link LocationListener} that will be called when updates are available
+     * @throws IllegalArgumentException if criteria, listener, or looper is null
+     * @throws SecurityException        if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(
+            long minTimeMs,
+            float minDistanceM,
+            @NonNull Criteria criteria,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull LocationListener listener) {
+        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+                criteria, minTimeMs, minDistanceM, false);
+        requestLocationUpdates(request, executor, listener);
+    }
+
+    /**
+     * Register for location updates using the named provider, and a
+     * pending intent.
+     *
+     * <p>See {@link #requestLocationUpdates(long, float, Criteria, PendingIntent)}
+     * for more detail on how to use this method.
+     *
+     * @param provider the name of the provider with which to register
+     * @param minTime minimum time interval between location updates, in milliseconds
+     * @param minDistance minimum distance between location updates, in meters
+     * @param intent a {@link PendingIntent} to be sent for each location update
+     *
+     * @throws IllegalArgumentException if provider is null or doesn't exist
+     * on this device
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(@NonNull String provider, long minTime, float minDistance,
+            @NonNull PendingIntent intent) {
+        checkProvider(provider);
+        checkPendingIntent(intent);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                provider, minTime, minDistance, false);
+        requestLocationUpdates(request, intent);
+    }
+
+    /**
+     * Register for location updates using a Criteria and pending intent.
+     *
+     * <p>The <code>requestLocationUpdates()</code> and
+     * <code>requestSingleUpdate()</code> register the current activity to be
+     * updated periodically by the named provider, or by the provider matching
+     * the specified {@link Criteria}, with location and status updates.
+     *
+     * <p> It may take a while to receive the first location update. If
+     * an immediate location is required, applications may use the
+     * {@link #getLastKnownLocation(String)} method.
+     *
+     * <p> Location updates are received either by {@link LocationListener}
+     * callbacks, or by broadcast intents to a supplied {@link PendingIntent}.
+     *
+     * <p> If the caller supplied a pending intent, then location updates
+     * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a
+     * {@link android.location.Location} value.
+     *
+     * <p> The location update interval can be controlled using the minTime parameter.
+     * The elapsed time between location updates will never be less than
+     * minTime, although it can be more depending on the Location Provider
+     * implementation and the update interval requested by other applications.
+     *
+     * <p> Choosing a sensible value for minTime is important to conserve
+     * battery life. Each location update requires power from
+     * GPS, WIFI, Cell and other radios. Select a minTime value as high as
+     * possible while still providing a reasonable user experience.
+     * If your application is not in the foreground and showing
+     * location to the user then your application should avoid using an active
+     * provider (such as {@link #NETWORK_PROVIDER} or {@link #GPS_PROVIDER}),
+     * but if you insist then select a minTime of 5 * 60 * 1000 (5 minutes)
+     * or greater. If your application is in the foreground and showing
+     * location to the user then it is appropriate to select a faster
+     * update interval.
+     *
+     * <p> The minDistance parameter can also be used to control the
+     * frequency of location updates. If it is greater than 0 then the
+     * location provider will only send your application an update when
+     * the location has changed by at least minDistance meters, AND
+     * at least minTime milliseconds have passed. However it is more
+     * difficult for location providers to save power using the minDistance
+     * parameter, so minTime should be the primary tool to conserving battery
+     * life.
+     *
+     * <p> If your application wants to passively observe location
+     * updates triggered by other applications, but not consume
+     * any additional power otherwise, then use the {@link #PASSIVE_PROVIDER}
+     * This provider does not actively turn on or modify active location
+     * providers, so you do not need to be as careful about minTime and
+     * minDistance. However if your application performs heavy work
+     * on a location update (such as network activity) then you should
+     * select non-zero values for minTime and/or minDistance to rate-limit
+     * your update frequency in the case another application enables a
+     * location provider with extremely fast updates.
+     *
+     * <p>In case the provider is disabled by the user, updates will stop,
+     * and a provider availability update will be sent.
+     * As soon as the provider is enabled again,
+     * location updates will immediately resume and a provider availability
+     * update sent. Providers can also send status updates, at any time,
+     * with extra's specific to the provider. If a callback was supplied
+     * then status and availability updates are via
+     * {@link LocationListener#onProviderDisabled},
+     * {@link LocationListener#onProviderEnabled} or
+     * {@link LocationListener#onStatusChanged}. Alternately, if a
+     * pending intent was supplied then status and availability updates
+     * are broadcast intents with extra keys of
+     * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED}.
+     *
+     * <p> If a {@link LocationListener} is used but with no Looper specified
+     * then the calling thread must already
+     * be a {@link android.os.Looper} thread such as the main thread of the
+     * calling Activity. If a Looper is specified with a {@link LocationListener}
+     * then callbacks are made on the supplied Looper thread.
+     *
+     * <p> When location callbacks are invoked, the system will hold a wakelock
+     * on your application's behalf for some period of time, but not
+     * indefinitely. If your application requires a long running wakelock
+     * within the location callback, you should acquire it yourself.
+     *
+     * <p class="note"> Prior to Jellybean, the minTime parameter was
+     * only a hint, and some location provider implementations ignored it.
+     * From Jellybean and onwards it is mandatory for Android compatible
+     * devices to observe both the minTime and minDistance parameters.
+     *
+     * @param minTime minimum time interval between location updates, in milliseconds
+     * @param minDistance minimum distance between location updates, in meters
+     * @param criteria contains parameters for the location manager to choose the
+     * appropriate provider and parameters to compute the location
+     * @param intent a {@link PendingIntent} to be sent for each location update
+     *
+     * @throws IllegalArgumentException if criteria is null
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if no suitable permission is present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(long minTime, float minDistance, @NonNull Criteria criteria,
+            @NonNull PendingIntent intent) {
+        checkCriteria(criteria);
+        checkPendingIntent(intent);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedCriteria(
+                criteria, minTime, minDistance, false);
+        requestLocationUpdates(request, intent);
+    }
+
+    /**
+     * Register for fused location updates using a LocationRequest and callback.
+     *
+     * <p>Upon a location update, the system delivers the new {@link Location} to the
+     * provided {@link LocationListener}, by calling its {@link
+     * LocationListener#onLocationChanged} method.</p>
+     *
+     * <p>The system will automatically select and enable the best providers
+     * to compute a location for your application. It may use only passive
+     * locations, or just a single location source, or it may fuse together
+     * multiple location sources in order to produce the best possible
+     * result, depending on the quality of service requested in the
+     * {@link LocationRequest}.
+     *
+     * <p>LocationRequest can be null, in which case the system will choose
+     * default, low power parameters for location updates. You will occasionally
+     * receive location updates as available, without a major power impact on the
+     * system. If your application just needs an occasional location update
+     * without any strict demands, then pass a null LocationRequest.
+     *
+     * <p>Only one LocationRequest can be registered for each unique callback
+     * or pending intent. So a subsequent request with the same callback or
+     * pending intent will over-write the previous LocationRequest.
+     *
+     * <p> If a pending intent is supplied then location updates
+     * are sent with a key of {@link #KEY_LOCATION_CHANGED} and a
+     * {@link android.location.Location} value. If a callback is supplied
+     * then location updates are made using the
+     * {@link LocationListener#onLocationChanged} callback, on the specified
+     * Looper thread. If a {@link LocationListener} is used
+     * but with a null Looper then the calling thread must already
+     * be a {@link android.os.Looper} thread (such as the main thread) and
+     * callbacks will occur on this thread.
+     *
+     * <p> Provider status updates and availability updates are deprecated
+     * because the system is performing provider fusion on the applications
+     * behalf. So {@link LocationListener#onProviderDisabled},
+     * {@link LocationListener#onProviderEnabled}, {@link LocationListener#onStatusChanged}
+     * will not be called, and intents with extra keys of
+     * {@link #KEY_PROVIDER_ENABLED} or {@link #KEY_STATUS_CHANGED} will not
+     * be received.
+     *
+     * <p> To unregister for Location updates, use: {@link #removeUpdates(LocationListener)}.
+     *
+     * @param locationRequest quality of service required, null for default low power
+     * @param listener a {@link LocationListener} whose
+     * {@link LocationListener#onLocationChanged} method will be called when
+     * the location update is available
+     * @param looper a Looper object whose message queue will be used to
+     * implement the callback mechanism, or null to make callbacks on the calling
+     * thread
+     *
+     * @throws IllegalArgumentException if listener is null
+     * @throws SecurityException if no suitable permission is present
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(
+            @NonNull LocationRequest locationRequest,
+            @NonNull LocationListener listener,
+            @Nullable Looper looper) {
+        requestLocationUpdates(locationRequest,
+                new LocationListenerTransport(looper == null ? new Handler() : new Handler(looper),
+                        listener));
+    }
+
+    /**
+     * Register for location updates with the given {@link LocationRequest}.
+     *
+     * <p>See {@link #requestLocationUpdates(String, long, float, Executor, LocationListener)} for
+     * more information.
+     *
+     * @param locationRequest the {@link LocationRequest} being made
+     * @param executor        all listener updates will take place on this {@link Executor}
+     * @param listener        a {@link LocationListener} that will be called when updates are
+     *                        available
+     * @throws IllegalArgumentException if locationRequest, listener, or executor is null
+     * @throws SecurityException        if no suitable permission is present
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(
+            @NonNull LocationRequest locationRequest,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull LocationListener listener) {
+        requestLocationUpdates(locationRequest, new LocationListenerTransport(executor, listener));
+    }
+
+    /**
+     * Register for fused location updates using a LocationRequest and a pending intent.
+     *
+     * <p>Upon a location update, the system delivers the new {@link Location} with your provided
+     * {@link PendingIntent}, as the value for {@link LocationManager#KEY_LOCATION_CHANGED}
+     * in the intent's extras.</p>
+     *
+     * <p> To unregister for Location updates, use: {@link #removeUpdates(PendingIntent)}.
+     *
+     * <p> See {@link #requestLocationUpdates(LocationRequest, LocationListener, Looper)}
+     * for more detail.
+     *
+     * @param locationRequest quality of service required, null for default low power
+     * @param pendingIntent a {@link PendingIntent} to be sent for the location update
+     *
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if no suitable permission is present
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void requestLocationUpdates(
+            @NonNull LocationRequest locationRequest,
+            @NonNull PendingIntent pendingIntent) {
+        Preconditions.checkArgument(locationRequest != null, "invalid null location request");
+        Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
+        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN) {
+            Preconditions.checkArgument(pendingIntent.isTargetedToPackage(),
+                    "pending intent must be targeted to package");
+        }
+
+        try {
+            mService.requestLocationUpdates(locationRequest, null, pendingIntent,
+                    mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void requestLocationUpdates(LocationRequest request,
+            LocationListenerTransport transport) {
+        synchronized (mListeners) {
+            LocationListenerTransport oldTransport = mListeners.put(transport.getKey(), transport);
+            if (oldTransport != null) {
+                oldTransport.unregisterListener();
+            }
+
+            try {
+                mService.requestLocationUpdates(request, transport, null,
+                        mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Set the last known location with a new location.
+     *
+     * <p>A privileged client can inject a {@link Location} if it has a better estimate of what
+     * the recent location is.  This is especially useful when the device boots up and the GPS
+     * chipset is in the process of getting the first fix.  If the client has cached the location,
+     * it can inject the {@link Location}, so if an app requests for a {@link Location} from {@link
+     * #getLastKnownLocation(String)}, the location information is still useful before getting
+     * the first fix.</p>
+     *
+     * <p> Useful in products like Auto.
+     *
+     * @param newLocation newly available {@link Location} object
+     * @return true if update was successful, false if not
+     *
+     * @throws SecurityException if no suitable permission is present
+     *
+     * @hide
+     */
+    @RequiresPermission(allOf = {LOCATION_HARDWARE, ACCESS_FINE_LOCATION})
+    public boolean injectLocation(@NonNull Location newLocation) {
+        try {
+            return mService.injectLocation(newLocation);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes location updates for the specified LocationListener. Following this call, updates
+     * will no longer occur for this listener.
+     *
+     * @param listener listener that no longer needs location updates
+     * @throws IllegalArgumentException if listener is null
+     */
+    public void removeUpdates(@NonNull LocationListener listener) {
+        Preconditions.checkArgument(listener != null, "invalid null listener");
+
+        synchronized (mListeners) {
+            LocationListenerTransport transport = mListeners.remove(listener);
+            if (transport == null) {
+                return;
+            }
+            transport.unregisterListener();
+
+            try {
+                mService.removeUpdates(transport, null, mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Removes all location updates for the specified pending intent. Following this call, updates
+     * will no longer occur for this pending intent.
+     *
+     * @param pendingIntent pending intent that no longer needs location updates
+     * @throws IllegalArgumentException if pendingIntent is null
+     */
+    public void removeUpdates(@NonNull PendingIntent pendingIntent) {
+        Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
+
+        try {
+            mService.removeUpdates(null, pendingIntent, mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of the names of all known location providers.
+     * <p>All providers are returned, including ones that are not permitted to
+     * be accessed by the calling activity or are currently disabled.
+     *
+     * @return list of Strings containing names of the provider
+     */
+    public @NonNull List<String> getAllProviders() {
+        try {
+            return mService.getAllProviders();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of the names of location providers.
+     *
+     * @param enabledOnly if true then only the providers which are currently
+     * enabled are returned.
+     * @return list of Strings containing names of the providers
+     */
+    public @NonNull List<String> getProviders(boolean enabledOnly) {
+        try {
+            return mService.getProviders(null, enabledOnly);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of the names of LocationProviders that satisfy the given
+     * criteria, or null if none do.  Only providers that are permitted to be
+     * accessed by the calling activity will be returned.
+     *
+     * @param criteria the criteria that the returned providers must match
+     * @param enabledOnly if true then only the providers which are currently
+     * enabled are returned.
+     * @return list of Strings containing names of the providers
+     */
+    public @NonNull List<String> getProviders(@NonNull Criteria criteria, boolean enabledOnly) {
+        checkCriteria(criteria);
+        try {
+            return mService.getProviders(criteria, enabledOnly);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the name of the provider that best meets the given criteria. Only providers
+     * that are permitted to be accessed by the calling activity will be
+     * returned.  If several providers meet the criteria, the one with the best
+     * accuracy is returned.  If no provider meets the criteria,
+     * the criteria are loosened in the following sequence:
+     *
+     * <ul>
+     * <li> power requirement
+     * <li> accuracy
+     * <li> bearing
+     * <li> speed
+     * <li> altitude
+     * </ul>
+     *
+     * <p> Note that the requirement on monetary cost is not removed
+     * in this process.
+     *
+     * @param criteria the criteria that need to be matched
+     * @param enabledOnly if true then only a provider that is currently enabled is returned
+     * @return name of the provider that best matches the requirements
+     */
+    public @Nullable String getBestProvider(@NonNull Criteria criteria, boolean enabledOnly) {
+        checkCriteria(criteria);
+        try {
+            return mService.getBestProvider(criteria, enabledOnly);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the information associated with the location provider of the
+     * given name, or null if no provider exists by that name.
+     *
+     * @param name the provider name
+     * @return a LocationProvider, or null
+     *
+     * @throws IllegalArgumentException if name is null or does not exist
+     * @throws SecurityException if the caller is not permitted to access the
+     * given provider.
+     */
+    public @Nullable LocationProvider getProvider(@NonNull String name) {
+        checkProvider(name);
+        try {
+            ProviderProperties properties = mService.getProviderProperties(name);
+            if (properties == null) {
+                return null;
+            }
+            return new LocationProvider(name, properties);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns true if the given package name matches a location provider package, and false
+     * otherwise.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    public boolean isProviderPackage(@NonNull String packageName) {
+        try {
+            return mService.isProviderPackage(packageName);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+            return false;
+        }
+    }
+
+    /**
+     * Sends additional commands to a location provider. Can be used to support provider specific
+     * extensions to the Location Manager API.
+     *
+     * @param provider name of the location provider.
+     * @param command  name of the command to send to the provider.
+     * @param extras   optional arguments for the command (or null).
+     * @return true always
+     */
+    public boolean sendExtraCommand(
+            @NonNull String provider, @NonNull String command, @Nullable Bundle extras) {
+        Preconditions.checkArgument(provider != null, "invalid null provider");
+        Preconditions.checkArgument(command != null, "invalid null command");
+
+        try {
+            return mService.sendExtraCommand(provider, command, extras);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Creates a mock location provider and adds it to the set of active providers.
      *
      * @param name the provider name
@@ -1543,44 +1447,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.
@@ -1599,199 +1478,308 @@
         }
     }
 
-    // --- GPS-specific support ---
+    /**
+     * Set a proximity alert for the location given by the position
+     * (latitude, longitude) and the given radius.
+     *
+     * <p> When the device
+     * detects that it has entered or exited the area surrounding the
+     * location, the given PendingIntent will be used to create an Intent
+     * to be fired.
+     *
+     * <p> The fired Intent will have a boolean extra added with key
+     * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is
+     * entering the proximity region; if false, it is exiting.
+     *
+     * <p> Due to the approximate nature of position estimation, if the
+     * device passes through the given area briefly, it is possible
+     * that no Intent will be fired.  Similarly, an Intent could be
+     * fired if the device passes very close to the given area but
+     * does not actually enter it.
+     *
+     * <p> After the number of milliseconds given by the expiration
+     * parameter, the location manager will delete this proximity
+     * alert and no longer monitor it.  A value of -1 indicates that
+     * there should be no expiration time.
+     *
+     * <p> Internally, this method uses both {@link #NETWORK_PROVIDER}
+     * and {@link #GPS_PROVIDER}.
+     *
+     * <p>Before API version 17, this method could be used with
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     * From API version 17 and onwards, this method requires
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission.
+     *
+     * @param latitude the latitude of the central point of the
+     * alert region
+     * @param longitude the longitude of the central point of the
+     * alert region
+     * @param radius the radius of the central point of the
+     * alert region, in meters
+     * @param expiration time for this proximity alert, in milliseconds,
+     * or -1 to indicate no expiration
+     * @param intent a PendingIntent that will be used to generate an Intent to
+     * fire when entry to or exit from the alert region is detected
+     *
+     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+     * permission is not present
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void addProximityAlert(double latitude, double longitude, float radius, long expiration,
+            @NonNull PendingIntent intent) {
+        checkPendingIntent(intent);
+        if (expiration < 0) expiration = Long.MAX_VALUE;
 
-    // This class is used to send Gnss status events to the client's specific thread.
-    private class GnssStatusListenerTransport extends IGnssStatusListener.Stub {
+        Geofence fence = Geofence.createCircle(latitude, longitude, radius);
+        LocationRequest request = new LocationRequest().setExpireIn(expiration);
+        try {
+            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
-        private final GnssStatus.Callback mGnssCallback;
-        private final OnNmeaMessageListener mGnssNmeaListener;
+    /**
+     * Removes the proximity alert with the given PendingIntent.
+     *
+     * <p>Before API version 17, this method could be used with
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     * From API version 17 and onwards, this method requires
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission.
+     *
+     * @param intent the PendingIntent that no longer needs to be notified of
+     * proximity alerts
+     *
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+     * permission is not present
+     */
+    public void removeProximityAlert(@NonNull PendingIntent intent) {
+        checkPendingIntent(intent);
+        String packageName = mContext.getPackageName();
 
-        private class GnssHandler extends Handler {
-            GnssHandler(Handler handler) {
-                super(handler != null ? handler.getLooper() : Looper.myLooper());
+        try {
+            mService.removeGeofence(null, intent, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Add a geofence with the specified LocationRequest quality of service.
+     *
+     * <p> When the device
+     * detects that it has entered or exited the area surrounding the
+     * location, the given PendingIntent will be used to create an Intent
+     * to be fired.
+     *
+     * <p> The fired Intent will have a boolean extra added with key
+     * {@link #KEY_PROXIMITY_ENTERING}. If the value is true, the device is
+     * entering the proximity region; if false, it is exiting.
+     *
+     * <p> The geofence engine fuses results from all location providers to
+     * provide the best balance between accuracy and power. Applications
+     * can choose the quality of service required using the
+     * {@link LocationRequest} object. If it is null then a default,
+     * low power geo-fencing implementation is used. It is possible to cross
+     * a geo-fence without notification, but the system will do its best
+     * to detect, using {@link LocationRequest} as a hint to trade-off
+     * accuracy and power.
+     *
+     * <p> The power required by the geofence engine can depend on many factors,
+     * such as quality and interval requested in {@link LocationRequest},
+     * distance to nearest geofence and current device velocity.
+     *
+     * @param request quality of service required, null for default low power
+     * @param fence a geographical description of the geofence area
+     * @param intent pending intent to receive geofence updates
+     *
+     * @throws IllegalArgumentException if fence is null
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+     * permission is not present
+     *
+     * @hide
+     */
+    @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+    public void addGeofence(
+            @NonNull LocationRequest request,
+            @NonNull Geofence fence,
+            @NonNull PendingIntent intent) {
+        checkPendingIntent(intent);
+        checkGeofence(fence);
+
+        try {
+            mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a single geofence.
+     *
+     * <p>This removes only the specified geofence associated with the
+     * specified pending intent. All other geofences remain unchanged.
+     *
+     * @param fence a geofence previously passed to {@link #addGeofence}
+     * @param intent a pending intent previously passed to {@link #addGeofence}
+     *
+     * @throws IllegalArgumentException if fence is null
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+     * permission is not present
+     *
+     * @hide
+     */
+    public void removeGeofence(@NonNull Geofence fence, @NonNull PendingIntent intent) {
+        checkPendingIntent(intent);
+        checkGeofence(fence);
+        String packageName = mContext.getPackageName();
+
+        try {
+            mService.removeGeofence(fence, intent, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove all geofences registered to the specified pending intent.
+     *
+     * @param intent a pending intent previously passed to {@link #addGeofence}
+     *
+     * @throws IllegalArgumentException if intent is null
+     * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+     * permission is not present
+     *
+     * @hide
+     */
+    public void removeAllGeofences(@NonNull PendingIntent intent) {
+        checkPendingIntent(intent);
+        String packageName = mContext.getPackageName();
+
+        try {
+            mService.removeGeofence(null, intent, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    // ================= GNSS APIs =================
+
+    /**
+     * Returns the supported capabilities of the GNSS chipset.
+     *
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public @NonNull GnssCapabilities getGnssCapabilities() {
+        try {
+            long gnssCapabilities = mService.getGnssCapabilities(mContext.getPackageName());
+            if (gnssCapabilities == GnssCapabilities.INVALID_CAPABILITIES) {
+                gnssCapabilities = 0L;
             }
-
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case NMEA_RECEIVED:
-                        synchronized (mNmeaBuffer) {
-                            for (Nmea nmea : mNmeaBuffer) {
-                                mGnssNmeaListener.onNmeaMessage(nmea.mNmea, nmea.mTimestamp);
-                            }
-                            mNmeaBuffer.clear();
-                        }
-                        break;
-                    case GNSS_EVENT_STARTED:
-                        mGnssCallback.onStarted();
-                        break;
-                    case GNSS_EVENT_STOPPED:
-                        mGnssCallback.onStopped();
-                        break;
-                    case GNSS_EVENT_FIRST_FIX:
-                        mGnssCallback.onFirstFix(mTimeToFirstFix);
-                        break;
-                    case GNSS_EVENT_SATELLITE_STATUS:
-                        mGnssCallback.onSatelliteStatusChanged(mGnssStatus);
-                        break;
-                    default:
-                        break;
-                }
-            }
+            return GnssCapabilities.of(gnssCapabilities);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
+    }
 
-        private final Handler mGnssHandler;
-
-        private static final int NMEA_RECEIVED = 1;
-        private static final int GNSS_EVENT_STARTED = 2;
-        private static final int GNSS_EVENT_STOPPED = 3;
-        private static final int GNSS_EVENT_FIRST_FIX = 4;
-        private static final int GNSS_EVENT_SATELLITE_STATUS = 5;
-
-        private class Nmea {
-            long mTimestamp;
-            String mNmea;
-
-            Nmea(long timestamp, String nmea) {
-                mTimestamp = timestamp;
-                mNmea = nmea;
-            }
+    /**
+     * Returns the model year of the GNSS hardware and software build. More details, such as build
+     * date, may be available in {@link #getGnssHardwareModelName()}. May return 0 if the model year
+     * is less than 2016.
+     */
+    public int getGnssYearOfHardware() {
+        try {
+            return mService.getGnssYearOfHardware();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        private final ArrayList<Nmea> mNmeaBuffer;
+    }
 
-        GnssStatusListenerTransport(GnssStatus.Callback callback, Handler handler) {
-            mGnssCallback = callback;
-            mGnssHandler = new GnssHandler(handler);
-            mGnssNmeaListener = null;
-            mNmeaBuffer = null;
+    /**
+     * Returns the Model Name (including Vendor and Hardware/Software Version) of the GNSS hardware
+     * driver.
+     *
+     * <p> No device-specific serial number or ID is returned from this API.
+     *
+     * <p> Will return null when the GNSS hardware abstraction layer does not support providing
+     * this value.
+     */
+    @Nullable
+    public String getGnssHardwareModelName() {
+        try {
+            return mService.getGnssHardwareModelName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
+    }
 
-        GnssStatusListenerTransport(OnNmeaMessageListener listener, Handler handler) {
-            mGnssCallback = null;
-            mGnssHandler = new GnssHandler(handler);
-            mGnssNmeaListener = listener;
-            mNmeaBuffer = new ArrayList<>();
+    /**
+     * Retrieves information about the current status of the GPS engine.
+     * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
+     * callback to ensure that the data is copied atomically.
+     *
+     * The caller may either pass in a {@link GpsStatus} object to set with the latest
+     * status information, or pass null to create a new {@link GpsStatus} object.
+     *
+     * @param status object containing GPS status details, or null.
+     * @return status object containing updated GPS status.
+     * @deprecated GpsStatus APIs are deprecated, use {@link GnssStatus} APIs instead.
+     */
+    @Deprecated
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) {
+        if (status == null) {
+            status = new GpsStatus();
         }
-
-        GnssStatusListenerTransport(GpsStatus.Listener listener, Handler handler) {
-            mGnssHandler = new GnssHandler(handler);
-            mNmeaBuffer = null;
-            mGnssCallback = listener != null ? new GnssStatus.Callback() {
-                @Override
-                public void onStarted() {
-                    listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
-                }
-
-                @Override
-                public void onStopped() {
-                    listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
-                }
-
-                @Override
-                public void onFirstFix(int ttff) {
-                    listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
-                }
-
-                @Override
-                public void onSatelliteStatusChanged(GnssStatus status) {
-                    listener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
-                }
-            } : null;
-            mGnssNmeaListener = null;
+        // When mGnssStatus is null, that means that this method is called outside
+        // onGpsStatusChanged().  Return an empty status to maintain backwards compatibility.
+        GnssStatus gnssStatus = mGnssStatusListenerManager.getGnssStatus();
+        int ttff = mGnssStatusListenerManager.getTtff();
+        if (gnssStatus != null) {
+            status.setStatus(gnssStatus, ttff);
         }
-
-        @Override
-        public void onGnssStarted() {
-            if (mGnssCallback != null) {
-                mGnssHandler.obtainMessage(GNSS_EVENT_STARTED).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onGnssStopped() {
-            if (mGnssCallback != null) {
-                mGnssHandler.obtainMessage(GNSS_EVENT_STOPPED).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onFirstFix(int ttff) {
-            if (mGnssCallback != null) {
-                mTimeToFirstFix = ttff;
-                mGnssHandler.obtainMessage(GNSS_EVENT_FIRST_FIX).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onSvStatusChanged(int svCount, int[] prnWithFlags,
-                float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) {
-            if (mGnssCallback != null) {
-                mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths,
-                        carrierFreqs);
-
-                mGnssHandler.removeMessages(GNSS_EVENT_SATELLITE_STATUS);
-                mGnssHandler.obtainMessage(GNSS_EVENT_SATELLITE_STATUS).sendToTarget();
-            }
-        }
-
-        @Override
-        public void onNmeaReceived(long timestamp, String nmea) {
-            if (mGnssNmeaListener != null) {
-                synchronized (mNmeaBuffer) {
-                    mNmeaBuffer.add(new Nmea(timestamp, nmea));
-                }
-
-                mGnssHandler.removeMessages(NMEA_RECEIVED);
-                mGnssHandler.obtainMessage(NMEA_RECEIVED).sendToTarget();
-            }
-        }
+        return status;
     }
 
     /**
      * Adds a GPS status listener.
      *
      * @param listener GPS status listener object to register
-     *
      * @return true if the listener was successfully added
-     *
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
-     * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead.
+     * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer
+     * supported in apps targeting R and above.
      */
     @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean addGpsStatusListener(GpsStatus.Listener listener) {
-        boolean result;
-
-        if (mGpsStatusListeners.get(listener) != null) {
-            return true;
-        }
         try {
-            GnssStatusListenerTransport transport = new GnssStatusListenerTransport(listener, null);
-            result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
-            if (result) {
-                mGpsStatusListeners.put(listener, transport);
-            }
+            return mGnssStatusListenerManager.addListener(listener, new Handler());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-
-        return result;
     }
 
     /**
      * Removes a GPS status listener.
      *
      * @param listener GPS status listener object to remove
-     * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead.
+     * @deprecated use {@link #unregisterGnssStatusCallback(GnssStatus.Callback)} instead. No longer
+     * supported in apps targeting R and above.
      */
     @Deprecated
     public void removeGpsStatusListener(GpsStatus.Listener listener) {
         try {
-            GnssStatusListenerTransport transport = mGpsStatusListeners.remove(listener);
-            if (transport != null) {
-                mService.unregisterGnssStatusCallback(transport);
-            }
+            mGnssStatusListenerManager.removeListener(listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1801,11 +1789,12 @@
      * Registers a GNSS status callback.
      *
      * @param callback GNSS status callback object to register
-     *
      * @return true if the listener was successfully added
-     *
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+     * @deprecated Use {@link #registerGnssStatusCallback(GnssStatus.Callback, Handler)} or {@link
+     * #registerGnssStatusCallback(Executor, GnssStatus.Callback)} instead.
      */
+    @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
         return registerGnssStatusCallback(callback, null);
@@ -1815,33 +1804,41 @@
      * Registers a GNSS status callback.
      *
      * @param callback GNSS status callback object to register
-     * @param handler the handler that the callback runs on.
-     *
+     * @param handler  a handler with a looper that the callback runs on.
      * @return true if the listener was successfully added
-     *
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssStatusCallback(
             @NonNull GnssStatus.Callback callback, @Nullable Handler handler) {
-        boolean result;
-        synchronized (mGnssStatusListeners) {
-            if (mGnssStatusListeners.get(callback) != null) {
-                return true;
-            }
-            try {
-                GnssStatusListenerTransport transport =
-                        new GnssStatusListenerTransport(callback, handler);
-                result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
-                if (result) {
-                    mGnssStatusListeners.put(callback, transport);
-                }
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        if (handler == null) {
+            handler = new Handler();
         }
 
-        return result;
+        try {
+            return mGnssStatusListenerManager.addListener(callback, handler);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a GNSS status callback.
+     *
+     * @param callback GNSS status callback object to register
+     * @param executor the executor that the callback runs on.
+     * @return true if the listener was successfully added
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+     */
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean registerGnssStatusCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GnssStatus.Callback callback) {
+        try {
+            return mGnssStatusListenerManager.addListener(callback, executor);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -1850,15 +1847,10 @@
      * @param callback GNSS status callback object to remove
      */
     public void unregisterGnssStatusCallback(@NonNull GnssStatus.Callback callback) {
-        synchronized (mGnssStatusListeners) {
-            try {
-                GnssStatusListenerTransport transport = mGnssStatusListeners.remove(callback);
-                if (transport != null) {
-                    mService.unregisterGnssStatusCallback(transport);
-                }
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+        try {
+            mGnssStatusListenerManager.removeListener(callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -1893,11 +1885,12 @@
      * Adds an NMEA listener.
      *
      * @param listener a {@link OnNmeaMessageListener} object to register
-     *
      * @return true if the listener was successfully added
-     *
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+     * @deprecated Use {@link #addNmeaListener(OnNmeaMessageListener, Handler)} or {@link
+     * #addNmeaListener(Executor, OnNmeaMessageListener)} instead.
      */
+    @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean addNmeaListener(@NonNull OnNmeaMessageListener listener) {
         return addNmeaListener(listener, null);
@@ -1907,33 +1900,40 @@
      * Adds an NMEA listener.
      *
      * @param listener a {@link OnNmeaMessageListener} object to register
-     * @param handler the handler that the listener runs on.
-     *
+     * @param handler  a handler with the looper that the listener runs on.
      * @return true if the listener was successfully added
-     *
      * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean addNmeaListener(
             @NonNull OnNmeaMessageListener listener, @Nullable Handler handler) {
-        boolean result;
-
-        if (mGnssNmeaListeners.get(listener) != null) {
-            // listener is already registered
-            return true;
+        if (handler == null) {
+            handler = new Handler();
         }
         try {
-            GnssStatusListenerTransport transport =
-                    new GnssStatusListenerTransport(listener, handler);
-            result = mService.registerGnssStatusCallback(transport, mContext.getPackageName());
-            if (result) {
-                mGnssNmeaListeners.put(listener, transport);
-            }
+            return mGnssStatusListenerManager.addListener(listener, handler);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
+    }
 
-        return result;
+    /**
+     * Adds an NMEA listener.
+     *
+     * @param listener a {@link OnNmeaMessageListener} object to register
+     * @param executor the {@link Executor} that the listener runs on.
+     * @return true if the listener was successfully added
+     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
+     */
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean addNmeaListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnNmeaMessageListener listener) {
+        try {
+            return mGnssStatusListenerManager.addListener(listener, executor);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -1943,10 +1943,7 @@
      */
     public void removeNmeaListener(@NonNull OnNmeaMessageListener listener) {
         try {
-            GnssStatusListenerTransport transport = mGnssNmeaListeners.remove(listener);
-            if (transport != null) {
-                mService.unregisterGnssStatusCallback(transport);
-            }
+            mGnssStatusListenerManager.removeListener(listener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1967,71 +1964,6 @@
     }
 
     /**
-     * Registers a GPS Measurement callback.
-     *
-     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
-     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
-     */
-    @RequiresPermission(ACCESS_FINE_LOCATION)
-    public boolean registerGnssMeasurementsCallback(
-            @NonNull GnssMeasurementsEvent.Callback callback) {
-        return registerGnssMeasurementsCallback(callback, null);
-    }
-
-    /**
-     * Registers a GPS Measurement callback.
-     *
-     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
-     * @param handler the handler that the callback runs on.
-     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
-     */
-    @RequiresPermission(ACCESS_FINE_LOCATION)
-    public boolean registerGnssMeasurementsCallback(
-            @NonNull GnssMeasurementsEvent.Callback callback, @Nullable Handler handler) {
-        return mGnssMeasurementCallbackTransport.add(callback, handler);
-    }
-
-    /**
-     * Injects GNSS measurement corrections into the GNSS chipset.
-     *
-     * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
-     *     measurement corrections to be injected into the GNSS chipset.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(ACCESS_FINE_LOCATION)
-    public void injectGnssMeasurementCorrections(
-            @NonNull GnssMeasurementCorrections measurementCorrections) {
-        try {
-            mGnssMeasurementCallbackTransport.injectGnssMeasurementCorrections(
-                    measurementCorrections);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the supported capabilities of the GNSS chipset.
-     *
-     * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(ACCESS_FINE_LOCATION)
-    public @NonNull GnssCapabilities getGnssCapabilities() {
-        try {
-            long gnssCapabilities = mGnssMeasurementCallbackTransport.getGnssCapabilities();
-            if (gnssCapabilities == GnssCapabilities.INVALID_CAPABILITIES) {
-                gnssCapabilities = 0L;
-            }
-            return GnssCapabilities.of(gnssCapabilities);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * No-op method to keep backward-compatibility. Don't use it. Use {@link
      * #unregisterGnssMeasurementsCallback} instead.
      *
@@ -2046,13 +1978,91 @@
     public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {}
 
     /**
+     * Registers a GPS Measurement callback.
+     *
+     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
+     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     * @deprecated Use {@link
+     * #registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback, Handler)} or {@link
+     * #registerGnssMeasurementsCallback(Executor, GnssMeasurementsEvent.Callback)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean registerGnssMeasurementsCallback(
+            @NonNull GnssMeasurementsEvent.Callback callback) {
+        return registerGnssMeasurementsCallback(callback, null);
+    }
+
+    /**
+     * Registers a GPS Measurement callback.
+     *
+     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
+     * @param handler  the handler that the callback runs on.
+     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     */
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean registerGnssMeasurementsCallback(
+            @NonNull GnssMeasurementsEvent.Callback callback, @Nullable Handler handler) {
+        if (handler == null) {
+            handler = new Handler();
+        }
+        try {
+            return mGnssMeasurementsListenerManager.addListener(callback, handler);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a GPS Measurement callback.
+     *
+     * @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
+     * @param executor the executor that the callback runs on.
+     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     */
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean registerGnssMeasurementsCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GnssMeasurementsEvent.Callback callback) {
+        try {
+            return mGnssMeasurementsListenerManager.addListener(callback, executor);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Injects GNSS measurement corrections into the GNSS chipset.
+     *
+     * @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
+     *     measurement corrections to be injected into the GNSS chipset.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public void injectGnssMeasurementCorrections(
+            @NonNull GnssMeasurementCorrections measurementCorrections) {
+        Preconditions.checkArgument(measurementCorrections != null);
+        try {
+            mService.injectGnssMeasurementCorrections(
+                    measurementCorrections, mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Unregisters a GPS Measurement callback.
      *
      * @param callback a {@link GnssMeasurementsEvent.Callback} object to remove.
      */
     public void unregisterGnssMeasurementsCallback(
             @NonNull GnssMeasurementsEvent.Callback callback) {
-        mGnssMeasurementCallbackTransport.remove(callback);
+        try {
+            mGnssMeasurementsListenerManager.removeListener(callback);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2088,7 +2098,11 @@
      *
      * @param callback a {@link GnssNavigationMessage.Callback} object to register.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     * @deprecated Use {@link
+     * #registerGnssNavigationMessageCallback(GnssNavigationMessage.Callback, Handler)} or {@link
+     * #registerGnssNavigationMessageCallback(Executor, GnssNavigationMessage.Callback)} instead.
      */
+    @Deprecated
     public boolean registerGnssNavigationMessageCallback(
             @NonNull GnssNavigationMessage.Callback callback) {
         return registerGnssNavigationMessageCallback(callback, null);
@@ -2098,13 +2112,39 @@
      * Registers a GNSS Navigation Message callback.
      *
      * @param callback a {@link GnssNavigationMessage.Callback} object to register.
-     * @param handler the handler that the callback runs on.
+     * @param handler  the handler that the callback runs on.
      * @return {@code true} if the callback was added successfully, {@code false} otherwise.
      */
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean registerGnssNavigationMessageCallback(
             @NonNull GnssNavigationMessage.Callback callback, @Nullable Handler handler) {
-        return mGnssNavigationMessageCallbackTransport.add(callback, handler);
+        if (handler == null) {
+            handler = new Handler();
+        }
+
+        try {
+            return mGnssNavigationMessageListenerTransport.addListener(callback, handler);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a GNSS Navigation Message callback.
+     *
+     * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+     * @param executor the looper that the callback runs on.
+     * @return {@code true} if the callback was added successfully, {@code false} otherwise.
+     */
+    @RequiresPermission(ACCESS_FINE_LOCATION)
+    public boolean registerGnssNavigationMessageCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull GnssNavigationMessage.Callback callback) {
+        try {
+            return mGnssNavigationMessageListenerTransport.addListener(callback, executor);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2114,62 +2154,8 @@
      */
     public void unregisterGnssNavigationMessageCallback(
             @NonNull GnssNavigationMessage.Callback callback) {
-        mGnssNavigationMessageCallbackTransport.remove(callback);
-    }
-
-    /**
-     * Retrieves information about the current status of the GPS engine.
-     * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
-     * callback to ensure that the data is copied atomically.
-     *
-     * The caller may either pass in a {@link GpsStatus} object to set with the latest
-     * status information, or pass null to create a new {@link GpsStatus} object.
-     *
-     * @param status object containing GPS status details, or null.
-     * @return status object containing updated GPS status.
-     */
-    @Deprecated
-    @RequiresPermission(ACCESS_FINE_LOCATION)
-    public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) {
-        if (status == null) {
-            status = new GpsStatus();
-        }
-        // When mGnssStatus is null, that means that this method is called outside
-        // onGpsStatusChanged().  Return an empty status to maintain backwards compatibility.
-        if (mGnssStatus != null) {
-            status.setStatus(mGnssStatus, mTimeToFirstFix);
-        }
-        return status;
-    }
-
-    /**
-     * Returns the model year of the GNSS hardware and software build.
-     *
-     * <p> More details, such as build date, may be available in {@link #getGnssHardwareModelName()}.
-     *
-     * <p> May return 0 if the model year is less than 2016.
-     */
-    public int getGnssYearOfHardware() {
         try {
-            return mService.getGnssYearOfHardware();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns the Model Name (including Vendor and Hardware/Software Version) of the GNSS hardware
-     * driver.
-     *
-     * <p> No device-specific serial number or ID is returned from this API.
-     *
-     * <p> Will return null when the GNSS hardware abstraction layer does not support providing
-     * this value.
-     */
-    @Nullable
-    public String getGnssHardwareModelName() {
-        try {
-            return mService.getGnssHardwareModelName();
+            mGnssNavigationMessageListenerTransport.removeListener(callback);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2217,12 +2203,20 @@
     @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
     public boolean registerGnssBatchedLocationCallback(long periodNanos, boolean wakeOnFifoFull,
             @NonNull BatchedLocationCallback callback, @Nullable Handler handler) {
-        mBatchedLocationCallbackTransport.add(callback, handler);
+        if (handler == null) {
+            handler = new Handler();
+        }
 
-        try {
-            return mService.startGnssBatch(periodNanos, wakeOnFifoFull, mContext.getPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        synchronized (mBatchedLocationCallbackManager) {
+            try {
+                if (mBatchedLocationCallbackManager.addListener(callback, handler)) {
+                    return mService.startGnssBatch(periodNanos, wakeOnFifoFull,
+                            mContext.getPackageName());
+                }
+                return false;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -2256,34 +2250,14 @@
     @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
     public boolean unregisterGnssBatchedLocationCallback(
             @NonNull BatchedLocationCallback callback) {
-
-        mBatchedLocationCallbackTransport.remove(callback);
-
-        try {
-            return mService.stopGnssBatch();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Sends additional commands to a location provider. Can be used to support provider specific
-     * extensions to the Location Manager API.
-     *
-     * @param provider name of the location provider.
-     * @param command  name of the command to send to the provider.
-     * @param extras   optional arguments for the command (or null).
-     * @return true always
-     */
-    public boolean sendExtraCommand(
-            @NonNull String provider, @NonNull String command, @Nullable Bundle extras) {
-        Preconditions.checkArgument(provider != null, "invalid null provider");
-        Preconditions.checkArgument(command != null, "invalid null command");
-
-        try {
-            return mService.sendExtraCommand(provider, command, extras);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        synchronized (mBatchedLocationCallbackManager) {
+            try {
+                mBatchedLocationCallbackManager.removeListener(callback);
+                mService.stopGnssBatch();
+                return true;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -2341,117 +2315,391 @@
         }
     }
 
-    /**
-     * Returns true if the given package name matches a location provider package, and false
-     * otherwise.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-    public boolean isProviderPackage(@NonNull String packageName) {
-        try {
-            return mService.isProviderPackage(packageName);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-            return false;
+    private class LocationListenerTransport extends ILocationListener.Stub {
+
+        private final Executor mExecutor;
+        @Nullable private volatile LocationListener mListener;
+
+        private LocationListenerTransport(@NonNull Handler handler,
+                @NonNull LocationListener listener) {
+            Preconditions.checkArgument(handler != null, "invalid null handler");
+            Preconditions.checkArgument(listener != null, "invalid null listener");
+
+            mExecutor = new HandlerExecutor(handler);
+            mListener = listener;
+        }
+
+        private LocationListenerTransport(@NonNull Executor executor,
+                @NonNull LocationListener listener) {
+            Preconditions.checkArgument(executor != null, "invalid null executor");
+            Preconditions.checkArgument(listener != null, "invalid null listener");
+
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        private LocationListener getKey() {
+            return mListener;
+        }
+
+        private void unregisterListener() {
+            mListener = null;
+        }
+
+        @Override
+        public void onLocationChanged(Location location) {
+            try {
+                mExecutor.execute(() -> {
+                    try {
+                        LocationListener listener = mListener;
+                        if (listener == null) {
+                            return;
+                        }
+
+                        // we may be under the binder identity if a direct executor is used
+                        long identity = Binder.clearCallingIdentity();
+                        try {
+                            listener.onLocationChanged(location);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    } finally {
+                        locationCallbackFinished();
+                    }
+                });
+            } catch (RejectedExecutionException e) {
+                locationCallbackFinished();
+                throw e;
+            }
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+            try {
+                mExecutor.execute(() -> {
+                    try {
+                        LocationListener listener = mListener;
+                        if (listener == null) {
+                            return;
+                        }
+
+                        // we may be under the binder identity if a direct executor is used
+                        long identity = Binder.clearCallingIdentity();
+                        try {
+                            listener.onStatusChanged(provider, status, extras);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    } finally {
+                        locationCallbackFinished();
+                    }
+                });
+            } catch (RejectedExecutionException e) {
+                locationCallbackFinished();
+                throw e;
+            }
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+            try {
+                mExecutor.execute(() -> {
+                    try {
+                        LocationListener listener = mListener;
+                        if (listener == null) {
+                            return;
+                        }
+
+                        // we may be under the binder identity if a direct executor is used
+                        long identity = Binder.clearCallingIdentity();
+                        try {
+                            listener.onProviderEnabled(provider);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    } finally {
+                        locationCallbackFinished();
+                    }
+                });
+            } catch (RejectedExecutionException e) {
+                locationCallbackFinished();
+                throw e;
+            }
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+            try {
+                mExecutor.execute(() -> {
+                    try {
+                        LocationListener listener = mListener;
+                        if (listener == null) {
+                            return;
+                        }
+
+                        // we may be under the binder identity if a direct executor is used
+                        long identity = Binder.clearCallingIdentity();
+                        try {
+                            listener.onProviderDisabled(provider);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    } finally {
+                        locationCallbackFinished();
+                    }
+                });
+            } catch (RejectedExecutionException e) {
+                locationCallbackFinished();
+                throw e;
+            }
+        }
+
+        private void locationCallbackFinished() {
+            try {
+                mService.locationCallbackFinished(this);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
-    /**
-     * Set the extra location controller package for location services on the device.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
-    public void setExtraLocationControllerPackage(@Nullable String packageName) {
-        try {
-            mService.setExtraLocationControllerPackage(packageName);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+    private static class NmeaAdapter extends GnssStatus.Callback implements OnNmeaMessageListener {
+
+        private final OnNmeaMessageListener mListener;
+
+        private NmeaAdapter(OnNmeaMessageListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onNmeaMessage(String message, long timestamp) {
+            mListener.onNmeaMessage(message, timestamp);
         }
     }
 
-    /**
-     * Set the extra location controller package for location services on the device.
-     *
-     * @removed
-     * @deprecated Use {@link #setExtraLocationControllerPackage} instead.
-     * @hide
-     */
-    @Deprecated
-    @SystemApi
-    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
-    public void setLocationControllerExtraPackage(String packageName) {
-        try {
-            mService.setExtraLocationControllerPackage(packageName);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+    private class GnssStatusListenerManager extends
+            AbstractListenerManager<GnssStatus.Callback> {
+
+        @Nullable
+        private IGnssStatusListener mListenerTransport;
+
+        @Nullable
+        private volatile GnssStatus mGnssStatus;
+        private volatile int mTtff;
+
+        public GnssStatus getGnssStatus() {
+            return mGnssStatus;
+        }
+
+        public int getTtff() {
+            return mTtff;
+        }
+
+        public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Handler handler)
+                throws RemoteException {
+            return addInternal(listener, handler);
+        }
+
+        public boolean addListener(@NonNull OnNmeaMessageListener listener,
+                @NonNull Handler handler)
+                throws RemoteException {
+            return addInternal(listener, handler);
+        }
+
+        public boolean addListener(@NonNull OnNmeaMessageListener listener,
+                @NonNull Executor executor)
+                throws RemoteException {
+            return addInternal(listener, executor);
+        }
+
+        @Override
+        protected GnssStatus.Callback convertKey(Object listener) {
+            if (listener instanceof GnssStatus.Callback) {
+                return (GnssStatus.Callback) listener;
+            } else if (listener instanceof GpsStatus.Listener) {
+                return new GnssStatus.Callback() {
+                    private final GpsStatus.Listener mGpsListener = (GpsStatus.Listener) listener;
+
+                    @Override
+                    public void onStarted() {
+                        mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STARTED);
+                    }
+
+                    @Override
+                    public void onStopped() {
+                        mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_STOPPED);
+                    }
+
+                    @Override
+                    public void onFirstFix(int ttffMillis) {
+                        mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_FIRST_FIX);
+                    }
+
+                    @Override
+                    public void onSatelliteStatusChanged(GnssStatus status) {
+                        mGpsListener.onGpsStatusChanged(GpsStatus.GPS_EVENT_SATELLITE_STATUS);
+                    }
+                };
+            } else if (listener instanceof OnNmeaMessageListener) {
+                return new NmeaAdapter((OnNmeaMessageListener) listener);
+            } else {
+                throw new IllegalStateException();
+            }
+        }
+
+        @Override
+        protected boolean registerService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport == null);
+
+            mListenerTransport = new GnssStatusListener();
+            return mService.registerGnssStatusCallback(mListenerTransport,
+                    mContext.getPackageName());
+        }
+
+        @Override
+        protected void unregisterService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport != null);
+
+            mService.unregisterGnssStatusCallback(mListenerTransport);
+            mListenerTransport = null;
+        }
+
+        private class GnssStatusListener extends IGnssStatusListener.Stub {
+            @Override
+            public void onGnssStarted() {
+                execute(GnssStatus.Callback::onStarted);
+            }
+
+            @Override
+            public void onGnssStopped() {
+                execute(GnssStatus.Callback::onStopped);
+            }
+
+            @Override
+            public void onFirstFix(int ttff) {
+                mTtff = ttff;
+                execute((callback) -> callback.onFirstFix(ttff));
+            }
+
+            @Override
+            public void onSvStatusChanged(int svCount, int[] svidWithFlags, float[] cn0s,
+                    float[] elevations, float[] azimuths, float[] carrierFreqs) {
+                GnssStatus localStatus = new GnssStatus(svCount, svidWithFlags, cn0s, elevations,
+                        azimuths, carrierFreqs);
+                mGnssStatus = localStatus;
+                execute((callback) -> callback.onSatelliteStatusChanged(localStatus));
+            }
+
+            @Override
+            public void onNmeaReceived(long timestamp, String nmea) {
+                execute((callback) -> {
+                    if (callback instanceof NmeaAdapter) {
+                        ((NmeaAdapter) callback).onNmeaMessage(nmea, timestamp);
+                    }
+                });
+            }
         }
     }
 
-    /**
-     * Returns the extra location controller package on the device.
-     *
-     * @hide
-     */
-    @SystemApi
-    public @Nullable String getExtraLocationControllerPackage() {
-        try {
-            return mService.getExtraLocationControllerPackage();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-            return null;
+    private class GnssMeasurementsListenerManager extends
+            AbstractListenerManager<GnssMeasurementsEvent.Callback> {
+
+        @Nullable
+        private IGnssMeasurementsListener mListenerTransport;
+
+        @Override
+        protected boolean registerService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport == null);
+
+            mListenerTransport = new GnssMeasurementsListener();
+            return mService.addGnssMeasurementsListener(mListenerTransport,
+                    mContext.getPackageName());
+        }
+
+        @Override
+        protected void unregisterService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport != null);
+
+            mService.removeGnssMeasurementsListener(mListenerTransport);
+            mListenerTransport = null;
+        }
+
+        private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub {
+            @Override
+            public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) {
+                execute((callback) -> callback.onGnssMeasurementsReceived(event));
+            }
+
+            @Override
+            public void onStatusChanged(int status) {
+                execute((callback) -> callback.onStatusChanged(status));
+            }
         }
     }
 
-    /**
-     * Set whether the extra location controller package is currently enabled on the device.
-     *
-     * @removed
-     * @deprecated Use {@link #setExtraLocationControllerPackageEnabled} instead.
-     * @hide
-     */
-    @SystemApi
-    @Deprecated
-    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
-    public void setLocationControllerExtraPackageEnabled(boolean enabled) {
-        try {
-            mService.setExtraLocationControllerPackageEnabled(enabled);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+    private class GnssNavigationMessageListenerManager extends
+            AbstractListenerManager<GnssNavigationMessage.Callback> {
+
+        @Nullable
+        private IGnssNavigationMessageListener mListenerTransport;
+
+        @Override
+        protected boolean registerService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport == null);
+
+            mListenerTransport = new GnssNavigationMessageListener();
+            return mService.addGnssNavigationMessageListener(mListenerTransport,
+                    mContext.getPackageName());
+        }
+
+        @Override
+        protected void unregisterService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport != null);
+
+            mService.removeGnssNavigationMessageListener(mListenerTransport);
+            mListenerTransport = null;
+        }
+
+        private class GnssNavigationMessageListener extends IGnssNavigationMessageListener.Stub {
+            @Override
+            public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+                execute((listener) -> listener.onGnssNavigationMessageReceived(event));
+            }
+
+            @Override
+            public void onStatusChanged(int status) {
+                execute((listener) -> listener.onStatusChanged(status));
+            }
         }
     }
 
-    /**
-     * Set whether the extra location controller package is currently enabled on the device.
-     *
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(Manifest.permission.LOCATION_HARDWARE)
-    public void setExtraLocationControllerPackageEnabled(boolean enabled) {
-        try {
-            mService.setExtraLocationControllerPackageEnabled(enabled);
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+    private class BatchedLocationCallbackManager extends
+            AbstractListenerManager<BatchedLocationCallback> {
+
+        @Nullable
+        private IBatchedLocationCallback mListenerTransport;
+
+        @Override
+        protected boolean registerService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport == null);
+
+            mListenerTransport = new BatchedLocationCallback();
+            return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName());
+        }
+
+        @Override
+        protected void unregisterService() throws RemoteException {
+            Preconditions.checkState(mListenerTransport != null);
+
+            mService.removeGnssBatchingCallback();
+            mListenerTransport = null;
+        }
+
+        private class BatchedLocationCallback extends IBatchedLocationCallback.Stub {
+            @Override
+            public void onLocationBatch(List<Location> locations) {
+                execute((listener) -> listener.onLocationBatch(locations));
+            }
         }
     }
-
-    /**
-     * Returns whether extra location controller package is currently enabled on the device.
-     *
-     * @hide
-     */
-    @SystemApi
-    public boolean isExtraLocationControllerPackageEnabled() {
-        try {
-            return mService.isExtraLocationControllerPackageEnabled();
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-            return false;
-        }
-    }
-
 }
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 ff6921d..fe0f669 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -16,12 +16,11 @@
 
 java_sdk_library {
     name: "com.android.location.provider",
-    srcs: [
-        "java/**/*.java",
-        ":framework-srcs",
-    ],
+    srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
     libs: [
         "androidx.annotation_annotation",
+        "framework-all",
     ],
     api_packages: ["com.android.location.provider"],
 }
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 a59b3e7..2f75e44 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -63,26 +63,6 @@
     path: "apex/java",
 }
 
-filegroup {
-    name: "mediaplayer2-srcs",
-    srcs: [
-        "apex/java/android/media/CloseGuard.java",
-        "apex/java/android/media/DataSourceCallback.java",
-        "apex/java/android/media/DataSourceDesc.java",
-        "apex/java/android/media/UriDataSourceDesc.java",
-        "apex/java/android/media/FileDataSourceDesc.java",
-        "apex/java/android/media/Media2Utils.java",
-        "apex/java/android/media/MediaPlayer2Utils.java",
-        "apex/java/android/media/MediaPlayer2.java",
-        "apex/java/android/media/Media2HTTPService.java",
-        "apex/java/android/media/Media2HTTPConnection.java",
-        "apex/java/android/media/RoutingDelegate.java",
-        "apex/java/android/media/BufferingParams.java",
-        "apex/java/android/media/ProxyDataSourceCallback.java",
-    ],
-    path: "apex/java",
-}
-
 metalava_updatable_media_args = " --error UnhiddenSystemApi " +
     "--hide RequiresPermission " +
     "--hide MissingPermission --hide BroadcastBehavior " +
diff --git a/media/apex/java/android/media/DataSourceDesc.java b/media/apex/java/android/media/DataSourceDesc.java
deleted file mode 100644
index 9a9c74a..0000000
--- a/media/apex/java/android/media/DataSourceDesc.java
+++ /dev/null
@@ -1,384 +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.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.net.HttpCookie;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Data source descriptor.
- *
- * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
- * {@link MediaPlayer2#setNextDataSources} to set data source for playback.
- *
- * @hide
- */
-public class DataSourceDesc {
-    // intentionally less than long.MAX_VALUE
-    static final long LONG_MAX = 0x7ffffffffffffffL;
-
-    // keep consistent with native code
-    public static final long LONG_MAX_TIME_MS = LONG_MAX / 1000;
-    /**
-     * @hide
-     */
-    public static final long LONG_MAX_TIME_US = LONG_MAX_TIME_MS * 1000;
-
-    public static final long POSITION_UNKNOWN = LONG_MAX_TIME_MS;
-
-    private String mMediaId;
-    private long mStartPositionMs = 0;
-    private long mEndPositionMs = POSITION_UNKNOWN;
-
-    DataSourceDesc(String mediaId, long startPositionMs, long endPositionMs) {
-        mMediaId = mediaId;
-        mStartPositionMs = startPositionMs;
-        mEndPositionMs = endPositionMs;
-    }
-
-    /**
-     * Releases the resources held by this {@code DataSourceDesc} object.
-     */
-    void close() {
-    }
-
-    // Have to declare protected for finalize() since it is protected
-    // in the base class Object.
-    @Override
-    protected void finalize() throws Throwable {
-        close();
-    }
-
-    /**
-     * Return the media Id of data source.
-     * @return the media Id of data source
-     */
-    public @Nullable String getMediaId() {
-        return mMediaId;
-    }
-
-    /**
-     * Return the position in milliseconds at which the playback will start.
-     * @return the position in milliseconds at which the playback will start
-     */
-    public long getStartPosition() {
-        return mStartPositionMs;
-    }
-
-    /**
-     * Return the position in milliseconds at which the playback will end.
-     * {@link #POSITION_UNKNOWN} means ending at the end of source content.
-     * @return the position in milliseconds at which the playback will end
-     */
-    public long getEndPosition() {
-        return mEndPositionMs;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder("DataSourceDesc{");
-        sb.append("mMediaId=").append(mMediaId);
-        sb.append(", mStartPositionMs=").append(mStartPositionMs);
-        sb.append(", mEndPositionMs=").append(mEndPositionMs);
-        sb.append('}');
-        return sb.toString();
-    }
-
-    /**
-     * Builder for {@link DataSourceDesc}.
-     * <p>
-     * Here is an example where <code>Builder</code> is used to define the
-     * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance:
-     *
-     * <pre class="prettyprint">
-     * DataSourceDesc newDSD = new DataSourceDesc.Builder()
-     *         .setDataSource(context, uri, headers, cookies)
-     *         .setStartPosition(1000)
-     *         .setEndPosition(15000)
-     *         .build();
-     * mediaplayer2.setDataSourceDesc(newDSD);
-     * </pre>
-     */
-    public static final class Builder {
-        private static final int SOURCE_TYPE_UNKNOWN = 0;
-        private static final int SOURCE_TYPE_URI = 1;
-        private static final int SOURCE_TYPE_FILE = 2;
-
-        private int mSourceType = SOURCE_TYPE_UNKNOWN;
-        private String mMediaId;
-        private long mStartPositionMs = 0;
-        private long mEndPositionMs = POSITION_UNKNOWN;
-
-        // For UriDataSourceDesc
-        private Uri mUri;
-        private Map<String, String> mHeader;
-        private List<HttpCookie> mCookies;
-
-        // For FileDataSourceDesc
-        private ParcelFileDescriptor mPFD;
-        private long mOffset = 0;
-        private long mLength = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
-
-        /**
-         * Constructs a new BuilderBase with the defaults.
-         */
-        public Builder() {
-        }
-
-        /**
-         * Constructs a new BuilderBase from a given {@link DataSourceDesc} instance
-         * @param dsd the {@link DataSourceDesc} object whose data will be reused
-         * in the new BuilderBase.
-         */
-        public Builder(@Nullable DataSourceDesc dsd) {
-            if (dsd == null) {
-                return;
-            }
-            mMediaId = dsd.mMediaId;
-            mStartPositionMs = dsd.mStartPositionMs;
-            mEndPositionMs = dsd.mEndPositionMs;
-            if (dsd instanceof FileDataSourceDesc) {
-                mSourceType = SOURCE_TYPE_FILE;
-                mPFD = ((FileDataSourceDesc) dsd).getParcelFileDescriptor();
-                mOffset = ((FileDataSourceDesc) dsd).getOffset();
-                mLength = ((FileDataSourceDesc) dsd).getLength();
-            } else if (dsd instanceof UriDataSourceDesc) {
-                mSourceType = SOURCE_TYPE_URI;
-                mUri = ((UriDataSourceDesc) dsd).getUri();
-                mHeader = ((UriDataSourceDesc) dsd).getHeaders();
-                mCookies = ((UriDataSourceDesc) dsd).getCookies();
-            } else {
-                throw new IllegalStateException("Unknown source type:" + mSourceType);
-            }
-        }
-
-        /**
-         * Sets all fields that have been set in the {@link DataSourceDesc} object.
-         * <code>IllegalStateException</code> will be thrown if there is conflict between fields.
-         *
-         * @return {@link DataSourceDesc}
-         */
-        @NonNull
-        public DataSourceDesc build() {
-            if (mSourceType == SOURCE_TYPE_UNKNOWN) {
-                throw new IllegalStateException("Source is not set.");
-            }
-            if (mStartPositionMs > mEndPositionMs) {
-                throw new IllegalStateException("Illegal start/end position: "
-                    + mStartPositionMs + " : " + mEndPositionMs);
-            }
-
-            DataSourceDesc desc;
-            if (mSourceType == SOURCE_TYPE_FILE) {
-                desc = new FileDataSourceDesc(
-                        mMediaId, mStartPositionMs, mEndPositionMs, mPFD, mOffset, mLength);
-            } else if (mSourceType == SOURCE_TYPE_URI) {
-                desc = new UriDataSourceDesc(
-                        mMediaId, mStartPositionMs, mEndPositionMs, mUri, mHeader, mCookies);
-            } else {
-                throw new IllegalStateException("Unknown source type:" + mSourceType);
-            }
-            return desc;
-        }
-
-        /**
-         * Sets the media Id of this data source.
-         *
-         * @param mediaId the media Id of this data source
-         * @return the same Builder instance.
-         */
-        @NonNull
-        public Builder setMediaId(@Nullable String mediaId) {
-            mMediaId = mediaId;
-            return this;
-        }
-
-        /**
-         * Sets the start position in milliseconds at which the playback will start.
-         * Any negative number is treated as 0.
-         *
-         * @param position the start position in milliseconds at which the playback will start
-         * @return the same Builder instance.
-         *
-         */
-        @NonNull
-        public Builder setStartPosition(long position) {
-            if (position < 0) {
-                position = 0;
-            }
-            mStartPositionMs = position;
-            return this;
-        }
-
-        /**
-         * Sets the end position in milliseconds at which the playback will end.
-         * Any negative number is treated as maximum duration {@link #LONG_MAX_TIME_MS}
-         * of the data source
-         *
-         * @param position the end position in milliseconds at which the playback will end
-         * @return the same Builder instance.
-         */
-        @NonNull
-        public Builder setEndPosition(long position) {
-            if (position < 0) {
-                position = LONG_MAX_TIME_MS;
-            }
-            mEndPositionMs = position;
-            return this;
-        }
-
-        /**
-         * Sets the data source as a content Uri.
-         *
-         * @param uri the Content URI of the data you want to play
-         * @return the same Builder instance.
-         * @throws NullPointerException if context or uri is null.
-         */
-        @NonNull
-        public Builder setDataSource(@NonNull Uri uri) {
-            setSourceType(SOURCE_TYPE_URI);
-            Media2Utils.checkArgument(uri != null, "uri cannot be null");
-            mUri = uri;
-            return this;
-        }
-
-        /**
-         * Sets the data source as a content Uri.
-         *
-         * To provide cookies for the subsequent HTTP requests, you can install your own default
-         * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you
-         * can use this API to pass the cookies as a list of HttpCookie. If the app has not
-         * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager
-         * and populates its CookieStore with the provided cookies when this data source is passed
-         * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler
-         * is required to be of CookieManager type such that {@link MediaPlayer2} can update the
-         * manager’s CookieStore.
-         *
-         *  <p><strong>Note</strong> that the cross domain redirection is allowed by default,
-         * but that can be changed with key/value pairs through the headers parameter with
-         * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
-         * disallow or allow cross domain redirection.
-         *
-         * @param uri the Content URI of the data you want to play
-         * @param headers the headers to be sent together with the request for the data
-         *                The headers must not include cookies. Instead, use the cookies param.
-         * @param cookies the cookies to be sent together with the request
-         * @return the same Builder instance.
-         * @throws NullPointerException if context or uri is null.
-         * @throws IllegalArgumentException if the cookie handler is not of CookieManager type
-         *                                  when cookies are provided.
-         */
-        @NonNull
-        public Builder setDataSource(@NonNull Uri uri, @Nullable Map<String, String> headers,
-                @Nullable List<HttpCookie> cookies) {
-            setSourceType(SOURCE_TYPE_URI);
-            Media2Utils.checkArgument(uri != null, "uri cannot be null");
-            if (cookies != null) {
-                CookieHandler cookieHandler = CookieHandler.getDefault();
-                if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
-                    throw new IllegalArgumentException(
-                            "The cookie handler has to be of CookieManager type "
-                                    + "when cookies are provided.");
-                }
-            }
-
-            mUri = uri;
-            if (headers != null) {
-                mHeader = new HashMap<String, String>(headers);
-            }
-            if (cookies != null) {
-                mCookies = new ArrayList<HttpCookie>(cookies);
-            }
-            return this;
-        }
-
-        /**
-         * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
-         * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
-         * created by this builder is passed to {@link MediaPlayer2} via
-         * {@link MediaPlayer2#setDataSource},
-         * {@link MediaPlayer2#setNextDataSource} or
-         * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
-         * close the ParcelFileDescriptor.
-         *
-         * @param pfd the ParcelFileDescriptor for the file to play
-         * @return the same Builder instance.
-         * @throws NullPointerException if pfd is null.
-         */
-        @NonNull
-        public Builder setDataSource(@NonNull ParcelFileDescriptor pfd) {
-            setSourceType(SOURCE_TYPE_FILE);
-            Media2Utils.checkArgument(pfd != null, "pfd cannot be null.");
-            mPFD = pfd;
-            return this;
-        }
-
-        /**
-         * Sets the data source (ParcelFileDescriptor) to use. The ParcelFileDescriptor must be
-         * seekable (N.B. a LocalSocket is not seekable). When the {@link DataSourceDesc}
-         * created by this builder is passed to {@link MediaPlayer2} via
-         * {@link MediaPlayer2#setDataSource},
-         * {@link MediaPlayer2#setNextDataSource} or
-         * {@link MediaPlayer2#setNextDataSources}, MediaPlayer2 will
-         * close the ParcelFileDescriptor.
-         *
-         * Any negative number for offset is treated as 0.
-         * Any negative number for length is treated as maximum length of the data source.
-         *
-         * @param pfd the ParcelFileDescriptor for the file to play
-         * @param offset the offset into the file where the data to be played starts, in bytes
-         * @param length the length in bytes of the data to be played
-         * @return the same Builder instance.
-         * @throws NullPointerException if pfd is null.
-         */
-        @NonNull
-        public Builder setDataSource(
-                @NonNull ParcelFileDescriptor pfd, long offset, long length) {
-            setSourceType(SOURCE_TYPE_FILE);
-            if (pfd == null) {
-                throw new NullPointerException("pfd cannot be null.");
-            }
-            if (offset < 0) {
-                offset = 0;
-            }
-            if (length < 0) {
-                length = FileDataSourceDesc.FD_LENGTH_UNKNOWN;
-            }
-            mPFD = pfd;
-            mOffset = offset;
-            mLength = length;
-            return this;
-        }
-
-        private void setSourceType(int type) {
-            if (mSourceType != SOURCE_TYPE_UNKNOWN) {
-                throw new IllegalStateException("Source is already set. type=" + mSourceType);
-            }
-            mSourceType = type;
-        }
-    }
-}
diff --git a/media/apex/java/android/media/FileDataSourceDesc.java b/media/apex/java/android/media/FileDataSourceDesc.java
deleted file mode 100644
index 2aa2cb7..0000000
--- a/media/apex/java/android/media/FileDataSourceDesc.java
+++ /dev/null
@@ -1,134 +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.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.IOException;
-
-/**
- * Structure of data source descriptor for sources using file descriptor.
- *
- * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
- * {@link MediaPlayer2#setNextDataSources} to set data source for playback.
- *
- * <p>Users should use {@link Builder} to create {@link FileDataSourceDesc}.
- * @hide
- */
-public class FileDataSourceDesc extends DataSourceDesc {
-    private static final String TAG = "FileDataSourceDesc";
-
-    /**
-     * Used when the length of file descriptor is unknown.
-     *
-     * @see #getLength()
-     */
-    public static final long FD_LENGTH_UNKNOWN = LONG_MAX;
-
-    private ParcelFileDescriptor mPFD;
-    private long mOffset = 0;
-    private long mLength = FD_LENGTH_UNKNOWN;
-    private int mCount = 0;
-    private boolean mClosed = false;
-
-    FileDataSourceDesc(String mediaId, long startPositionMs, long endPositionMs,
-            ParcelFileDescriptor pfd, long offset, long length) {
-        super(mediaId, startPositionMs, endPositionMs);
-        mPFD = pfd;
-        mOffset = offset;
-        mLength = length;
-    }
-
-    /**
-     * Releases the resources held by this {@code FileDataSourceDesc} object.
-     */
-    @Override
-    void close() {
-        super.close();
-        decCount();
-    }
-
-    /**
-     * Decrements usage count by {@link MediaPlayer2}.
-     * If this is the last usage, also releases the file descriptor held by this
-     * {@code FileDataSourceDesc} object.
-     */
-    void decCount() {
-        synchronized (this) {
-            --mCount;
-            if (mCount > 0) {
-                return;
-            }
-
-            try {
-                mPFD.close();
-                mClosed = true;
-            } catch (IOException e) {
-                Log.e(TAG, "failed to close pfd: " + e);
-            }
-        }
-    }
-
-    /**
-     * Increments usage count by {@link MediaPlayer2} if PFD has not been closed.
-     */
-    void incCount() {
-        synchronized (this) {
-            if (!mClosed) {
-                ++mCount;
-            }
-        }
-    }
-
-    /**
-     * Return the status of underline ParcelFileDescriptor
-     * @return true if underline ParcelFileDescriptor is closed, false otherwise.
-     */
-    boolean isPFDClosed() {
-        synchronized (this) {
-            return mClosed;
-        }
-    }
-
-    /**
-     * Return the ParcelFileDescriptor of this data source.
-     * @return the ParcelFileDescriptor of this data source
-     */
-    public @NonNull ParcelFileDescriptor getParcelFileDescriptor() {
-        return mPFD;
-    }
-
-    /**
-     * Return the offset associated with the ParcelFileDescriptor of this data source.
-     * It's meaningful only when it has been set by the {@link Builder}.
-     * @return the offset associated with the ParcelFileDescriptor of this data source
-     */
-    public long getOffset() {
-        return mOffset;
-    }
-
-    /**
-     * Return the content length associated with the ParcelFileDescriptor of this data source.
-     * {@link #FD_LENGTH_UNKNOWN} means same as the length of source content.
-     * @return the content length associated with the ParcelFileDescriptor of this data source
-     */
-    public long getLength() {
-        return mLength;
-    }
-}
diff --git a/media/apex/java/android/media/Media2HTTPConnection.java b/media/apex/java/android/media/Media2HTTPConnection.java
deleted file mode 100644
index a369a62..0000000
--- a/media/apex/java/android/media/Media2HTTPConnection.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright 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.media;
-
-import static android.media.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
-
-import android.os.StrictMode;
-import android.util.Log;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.CookieHandler;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.NoRouteToHostException;
-import java.net.ProtocolException;
-import java.net.Proxy;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.net.UnknownServiceException;
-import java.util.HashMap;
-import java.util.Map;
-
-/** @hide */
-public class Media2HTTPConnection {
-    private static final String TAG = "Media2HTTPConnection";
-    private static final boolean VERBOSE = false;
-
-    // connection timeout - 30 sec
-    private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
-
-    private long mCurrentOffset = -1;
-    private URL mURL = null;
-    private Map<String, String> mHeaders = null;
-    private HttpURLConnection mConnection = null;
-    private long mTotalSize = -1;
-    private InputStream mInputStream = null;
-
-    private boolean mAllowCrossDomainRedirect = true;
-    private boolean mAllowCrossProtocolRedirect = true;
-
-    // from com.squareup.okhttp.internal.http
-    private final static int HTTP_TEMP_REDIRECT = 307;
-    private final static int MAX_REDIRECTS = 20;
-
-    public Media2HTTPConnection() {
-        CookieHandler cookieHandler = CookieHandler.getDefault();
-        if (cookieHandler == null) {
-            Log.w(TAG, "Media2HTTPConnection: Unexpected. No CookieHandler found.");
-        }
-    }
-
-    public boolean connect(String uri, String headers) {
-        if (VERBOSE) {
-            Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
-        }
-
-        try {
-            disconnect();
-            mAllowCrossDomainRedirect = true;
-            mURL = new URL(uri);
-            mHeaders = convertHeaderStringToMap(headers);
-        } catch (MalformedURLException e) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean parseBoolean(String val) {
-        try {
-            return Long.parseLong(val) != 0;
-        } catch (NumberFormatException e) {
-            return "true".equalsIgnoreCase(val) ||
-                "yes".equalsIgnoreCase(val);
-        }
-    }
-
-    /* returns true iff header is internal */
-    private boolean filterOutInternalHeaders(String key, String val) {
-        if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
-            mAllowCrossDomainRedirect = parseBoolean(val);
-            // cross-protocol redirects are also controlled by this flag
-            mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
-        } else {
-            return false;
-        }
-        return true;
-    }
-
-    private Map<String, String> convertHeaderStringToMap(String headers) {
-        HashMap<String, String> map = new HashMap<String, String>();
-
-        String[] pairs = headers.split("\r\n");
-        for (String pair : pairs) {
-            int colonPos = pair.indexOf(":");
-            if (colonPos >= 0) {
-                String key = pair.substring(0, colonPos);
-                String val = pair.substring(colonPos + 1);
-
-                if (!filterOutInternalHeaders(key, val)) {
-                    map.put(key, val);
-                }
-            }
-        }
-
-        return map;
-    }
-
-    public void disconnect() {
-        teardownConnection();
-        mHeaders = null;
-        mURL = null;
-    }
-
-    private void teardownConnection() {
-        if (mConnection != null) {
-            if (mInputStream != null) {
-                try {
-                    mInputStream.close();
-                } catch (IOException e) {
-                }
-                mInputStream = null;
-            }
-
-            mConnection.disconnect();
-            mConnection = null;
-
-            mCurrentOffset = -1;
-        }
-    }
-
-    private static final boolean isLocalHost(URL url) {
-        if (url == null) {
-            return false;
-        }
-
-        String host = url.getHost();
-
-        if (host == null) {
-            return false;
-        }
-
-        try {
-            if (host.equalsIgnoreCase("localhost")) {
-                return true;
-            }
-            if (InetAddress.getByName(host).isLoopbackAddress()) {
-                return true;
-            }
-        } catch (IllegalArgumentException | UnknownHostException e) {
-        }
-        return false;
-    }
-
-    private void seekTo(long offset) throws IOException {
-        teardownConnection();
-
-        try {
-            int response;
-            int redirectCount = 0;
-
-            URL url = mURL;
-
-            // do not use any proxy for localhost (127.0.0.1)
-            boolean noProxy = isLocalHost(url);
-
-            while (true) {
-                if (noProxy) {
-                    mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
-                } else {
-                    mConnection = (HttpURLConnection)url.openConnection();
-                }
-                mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
-
-                // handle redirects ourselves if we do not allow cross-domain redirect
-                mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
-
-                if (mHeaders != null) {
-                    for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
-                        mConnection.setRequestProperty(
-                                entry.getKey(), entry.getValue());
-                    }
-                }
-
-                if (offset > 0) {
-                    mConnection.setRequestProperty(
-                            "Range", "bytes=" + offset + "-");
-                }
-
-                response = mConnection.getResponseCode();
-                if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
-                        response != HttpURLConnection.HTTP_MOVED_PERM &&
-                        response != HttpURLConnection.HTTP_MOVED_TEMP &&
-                        response != HttpURLConnection.HTTP_SEE_OTHER &&
-                        response != HTTP_TEMP_REDIRECT) {
-                    // not a redirect, or redirect handled by HttpURLConnection
-                    break;
-                }
-
-                if (++redirectCount > MAX_REDIRECTS) {
-                    throw new NoRouteToHostException("Too many redirects: " + redirectCount);
-                }
-
-                String method = mConnection.getRequestMethod();
-                if (response == HTTP_TEMP_REDIRECT &&
-                        !method.equals("GET") && !method.equals("HEAD")) {
-                    // "If the 307 status code is received in response to a
-                    // request other than GET or HEAD, the user agent MUST NOT
-                    // automatically redirect the request"
-                    throw new NoRouteToHostException("Invalid redirect");
-                }
-                String location = mConnection.getHeaderField("Location");
-                if (location == null) {
-                    throw new NoRouteToHostException("Invalid redirect");
-                }
-                url = new URL(mURL /* TRICKY: don't use url! */, location);
-                if (!url.getProtocol().equals("https") &&
-                        !url.getProtocol().equals("http")) {
-                    throw new NoRouteToHostException("Unsupported protocol redirect");
-                }
-                boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
-                if (!mAllowCrossProtocolRedirect && !sameProtocol) {
-                    throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
-                }
-                boolean sameHost = mURL.getHost().equals(url.getHost());
-                if (!mAllowCrossDomainRedirect && !sameHost) {
-                    throw new NoRouteToHostException("Cross-domain redirects are disallowed");
-                }
-
-                if (response != HTTP_TEMP_REDIRECT) {
-                    // update effective URL, unless it is a Temporary Redirect
-                    mURL = url;
-                }
-            }
-
-            if (mAllowCrossDomainRedirect) {
-                // remember the current, potentially redirected URL if redirects
-                // were handled by HttpURLConnection
-                mURL = mConnection.getURL();
-            }
-
-            if (response == HttpURLConnection.HTTP_PARTIAL) {
-                // Partial content, we cannot just use getContentLength
-                // because what we want is not just the length of the range
-                // returned but the size of the full content if available.
-
-                String contentRange =
-                    mConnection.getHeaderField("Content-Range");
-
-                mTotalSize = -1;
-                if (contentRange != null) {
-                    // format is "bytes xxx-yyy/zzz
-                    // where "zzz" is the total number of bytes of the
-                    // content or '*' if unknown.
-
-                    int lastSlashPos = contentRange.lastIndexOf('/');
-                    if (lastSlashPos >= 0) {
-                        String total =
-                            contentRange.substring(lastSlashPos + 1);
-
-                        try {
-                            mTotalSize = Long.parseLong(total);
-                        } catch (NumberFormatException e) {
-                        }
-                    }
-                }
-            } else if (response != HttpURLConnection.HTTP_OK) {
-                throw new IOException();
-            } else {
-                mTotalSize = mConnection.getContentLength();
-            }
-
-            if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
-                // Some servers simply ignore "Range" requests and serve
-                // data from the start of the content.
-                throw new ProtocolException();
-            }
-
-            mInputStream =
-                new BufferedInputStream(mConnection.getInputStream());
-
-            mCurrentOffset = offset;
-        } catch (IOException e) {
-            mTotalSize = -1;
-            teardownConnection();
-            mCurrentOffset = -1;
-
-            throw e;
-        }
-    }
-
-    public int readAt(long offset, byte[] data, int size) {
-        StrictMode.ThreadPolicy policy =
-            new StrictMode.ThreadPolicy.Builder().permitAll().build();
-
-        StrictMode.setThreadPolicy(policy);
-
-        try {
-            if (offset != mCurrentOffset) {
-                seekTo(offset);
-            }
-
-            int n = mInputStream.read(data, 0, size);
-
-            if (n == -1) {
-                // InputStream signals EOS using a -1 result, our semantics
-                // are to return a 0-length read.
-                n = 0;
-            }
-
-            mCurrentOffset += n;
-
-            if (VERBOSE) {
-                Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
-            }
-
-            return n;
-        } catch (ProtocolException e) {
-            Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
-            return MEDIA_ERROR_UNSUPPORTED;
-        } catch (NoRouteToHostException e) {
-            Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
-            return MEDIA_ERROR_UNSUPPORTED;
-        } catch (UnknownServiceException e) {
-            Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
-            return MEDIA_ERROR_UNSUPPORTED;
-        } catch (IOException e) {
-            if (VERBOSE) {
-                Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
-            }
-            return -1;
-        } catch (Exception e) {
-            if (VERBOSE) {
-                Log.d(TAG, "unknown exception " + e);
-                Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
-            }
-            return -1;
-        }
-    }
-
-    public long getSize() {
-        if (mConnection == null) {
-            try {
-                seekTo(0);
-            } catch (IOException e) {
-                return -1;
-            }
-        }
-
-        return mTotalSize;
-    }
-
-    public String getMIMEType() {
-        if (mConnection == null) {
-            try {
-                seekTo(0);
-            } catch (IOException e) {
-                return "application/octet-stream";
-            }
-        }
-
-        return mConnection.getContentType();
-    }
-
-    public String getUri() {
-        return mURL.toString();
-    }
-}
diff --git a/media/apex/java/android/media/Media2HTTPService.java b/media/apex/java/android/media/Media2HTTPService.java
deleted file mode 100644
index 0d46ce4..0000000
--- a/media/apex/java/android/media/Media2HTTPService.java
+++ /dev/null
@@ -1,58 +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.media;
-
-import android.util.Log;
-
-import java.net.HttpCookie;
-import java.util.List;
-
-/** @hide */
-public class Media2HTTPService {
-    private static final String TAG = "Media2HTTPService";
-    private List<HttpCookie> mCookies;
-    private Boolean mCookieStoreInitialized = new Boolean(false);
-
-    public Media2HTTPService(List<HttpCookie> cookies) {
-        mCookies = cookies;
-        Log.v(TAG, "Media2HTTPService(" + this + "): Cookies: " + cookies);
-    }
-
-    public Media2HTTPConnection makeHTTPConnection() {
-
-        synchronized (mCookieStoreInitialized) {
-            Media2Utils.storeCookies(mCookies);
-        }
-
-        return new Media2HTTPConnection();
-    }
-
-    /* package private */ static Media2HTTPService createHTTPService(String path) {
-        return createHTTPService(path, null);
-    }
-
-    // when cookies are provided
-    static Media2HTTPService createHTTPService(String path, List<HttpCookie> cookies) {
-        if (path.startsWith("http://") || path.startsWith("https://")) {
-            return (new Media2HTTPService(cookies));
-        } else if (path.startsWith("widevine://")) {
-            Log.d(TAG, "Widevine classic is no longer supported");
-        }
-
-        return null;
-    }
-}
diff --git a/media/apex/java/android/media/Media2Utils.java b/media/apex/java/android/media/Media2Utils.java
deleted file mode 100644
index a87e967..0000000
--- a/media/apex/java/android/media/Media2Utils.java
+++ /dev/null
@@ -1,78 +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.media;
-
-import android.util.Log;
-
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.net.CookieStore;
-import java.net.HttpCookie;
-import java.util.List;
-
-/** @hide */
-public class Media2Utils {
-    private static final String TAG = "Media2Utils";
-
-    private Media2Utils() {
-    }
-
-    /**
-     * Ensures that an expression checking an argument is true.
-     *
-     * @param expression the expression to check
-     * @param errorMessage the exception message to use if the check fails; will
-     *     be converted to a string using {@link String#valueOf(Object)}
-     * @throws IllegalArgumentException if {@code expression} is false
-     */
-    public static void checkArgument(boolean expression, String errorMessage) {
-        if (!expression) {
-            throw new IllegalArgumentException(errorMessage);
-        }
-    }
-
-    public static synchronized void storeCookies(List<HttpCookie> cookies) {
-        CookieHandler cookieHandler = CookieHandler.getDefault();
-        if (cookieHandler == null) {
-            cookieHandler = new CookieManager();
-            CookieHandler.setDefault(cookieHandler);
-            Log.v(TAG, "storeCookies: CookieManager created: " + cookieHandler);
-        } else {
-            Log.v(TAG, "storeCookies: CookieHandler (" + cookieHandler + ") exists.");
-        }
-
-        if (cookies != null) {
-            if (cookieHandler instanceof CookieManager) {
-                CookieManager cookieManager = (CookieManager)cookieHandler;
-                CookieStore store = cookieManager.getCookieStore();
-                for (HttpCookie cookie : cookies) {
-                    try {
-                        store.add(null, cookie);
-                    } catch (Exception e) {
-                        Log.v(TAG, "storeCookies: CookieStore.add" + cookie, e);
-                    }
-                }
-            } else {
-                Log.w(TAG, "storeCookies: The installed CookieHandler is not a CookieManager."
-                        + " Can’t add the provided cookies to the cookie store.");
-            }
-        }   // cookies
-
-        Log.v(TAG, "storeCookies: cookieHandler: " + cookieHandler + " Cookies: " + cookies);
-
-    }
-}
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
deleted file mode 100644
index 614d737..0000000
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ /dev/null
@@ -1,5507 +0,0 @@
-/*
- * Copyright 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.media;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.annotation.TestApi;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.media.MediaDrm.KeyRequest;
-import android.media.MediaPlayer2.DrmInfo;
-import android.media.MediaPlayer2Proto.PlayerMessage;
-import android.media.MediaPlayer2Proto.Value;
-import android.media.protobuf.InvalidProtocolBufferException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.net.HttpCookie;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.ByteOrder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-
-/**
- * MediaPlayer2 class can be used to control playback of audio/video files and streams.
- *
- * <p>
- * This API is not generally intended for third party application developers.
- * Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
- * <a href="{@docRoot}reference/androidx/media2/package-summary.html">Media2 Library</a>
- * for consistent behavior across all devices.
- *
- * <p>Topics covered here are:
- * <ol>
- * <li><a href="#PlayerStates">Player states</a>
- * <li><a href="#InvalidStates">Invalid method calls</a>
- * <li><a href="#Permissions">Permissions</a>
- * <li><a href="#Callbacks">Callbacks</a>
- * </ol>
- *
- *
- * <h3 id="PlayerStates">Player states</h3>
- *
- * <p>The playback control of audio/video files is managed as a state machine.</p>
- * <p><div style="text-align:center;"><img src="../../../images/mediaplayer2_state_diagram.png"
- *         alt="MediaPlayer2 State diagram"
- *         border="0" /></div></p>
- * <p>The MediaPlayer2 object has five states:</p>
- * <ol>
- *     <li><p>{@link #PLAYER_STATE_IDLE}: MediaPlayer2 is in the <strong>Idle</strong>
- *         state after it's created, or after calling {@link #reset()}.</p>
- *
- *         <p>While in this state, you should call
- *         {@link #setDataSource setDataSource}. It is a good
- *         programming practice to register an {@link EventCallback#onCallCompleted onCallCompleted}
- *         <a href="#Callbacks">callback</a> and watch for {@link #CALL_STATUS_BAD_VALUE} and
- *         {@link #CALL_STATUS_ERROR_IO}, which might be caused by <code>setDataSource</code>.
- *         </p>
- *
- *         <p>Calling {@link #prepare()} transfers a MediaPlayer2 object to
- *         the <strong>Prepared</strong> state. Note
- *         that {@link #prepare()} is asynchronous. When the preparation completes,
- *         if you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>,
- *         the player executes the callback
- *         with {@link #MEDIA_INFO_PREPARED} and transitions to the
- *         <strong>Prepared</strong> state.</p>
- *         </li>
- *
- *     <li>{@link #PLAYER_STATE_PREPARED}: A MediaPlayer object must be in the
- *         <strong>Prepared</strong> state before playback can be started for the first time.
- *         While in this state, you can set player properties
- *         such as audio/sound volume and looping by invoking the corresponding set methods.
- *         Calling {@link #play()} transfers a MediaPlayer2 object to
- *         the <strong>Playing</strong> state.
- *      </li>
- *
- *     <li>{@link #PLAYER_STATE_PLAYING}:
- *         <p>The player plays the data source while in this state.
- *         If you register an {@link EventCallback#onInfo onInfo} <a href="#Callbacks">callback</a>,
- *         the player regularly executes the callback with
- *         {@link #MEDIA_INFO_BUFFERING_UPDATE}.
- *         This allows applications to keep track of the buffering status
- *         while streaming audio/video.</p>
- *
- *         <p> When the playback reaches the end of stream, the behavior depends on whether or
- *         not you've enabled looping by calling {@link #loopCurrent}:</p>
- *         <ul>
- *         <li>If the looping mode was set to <code>false</code>, the player will transfer
- *         to the <strong>Paused</strong> state. If you registered an {@link EventCallback#onInfo
- *         onInfo} <a href="#Callbacks">callback</a>
- *         the player calls the callback with {@link #MEDIA_INFO_DATA_SOURCE_END} and enters
- *         the <strong>Paused</strong> state.
- *         </li>
- *         <li>If the looping mode was set to <code>true</code>,
- *         the MediaPlayer2 object remains in the <strong>Playing</strong> state and replays its
- *         data source from the beginning.</li>
- *         </ul>
- *         </li>
- *
- *     <li>{@link #PLAYER_STATE_PAUSED}: Audio/video playback pauses while in this state.
- *         Call {@link #play()} to resume playback from the position where it paused.</li>
- *
- *     <li>{@link #PLAYER_STATE_ERROR}: <p>In general, playback might fail due to various
- *          reasons such as unsupported audio/video format, poorly interleaved
- *          audio/video, resolution too high, streaming timeout, and others.
- *          In addition, due to programming errors, a playback
- *          control operation might be performed from an <a href="#InvalidStates">invalid state</a>.
- *          In these cases the player transitions to the <strong>Error</strong> state.</p>
- *
- *          <p>If you register an {@link EventCallback#onError onError}}
- *          <a href="#Callbacks">callback</a>,
- *          the callback will be performed when entering the state. When programming errors happen,
- *          such as calling {@link #prepare()} and
- *          {@link #setDataSource} methods
- *          from an <a href="#InvalidStates">invalid state</a>, the callback is called with
- *          {@link #CALL_STATUS_INVALID_OPERATION}. The MediaPlayer2 object enters the
- *          <strong>Error</strong> state whether or not a callback exists. </p>
- *
- *          <p>To recover from an error and reuse a MediaPlayer2 object that is in the <strong>
- *          Error</strong> state,
- *          call {@link #reset()}. The object will return to the <strong>Idle</strong>
- *          state and all state information will be lost.</p>
- *          </li>
- * </ol>
- *
- * <p>You should follow these best practices when coding an app that uses MediaPlayer2:</p>
- *
- * <ul>
- *
- * <li>Use <a href="#Callbacks">callbacks</a> to respond to state changes and errors.</li>
- *
- * <li>When  a MediaPlayer2 object is no longer being used, call {@link #close()} as soon as
- * possible to release the resources used by the internal player engine associated with the
- * MediaPlayer2. Failure to call {@link #close()} may cause subsequent instances of
- * MediaPlayer2 objects to fallback to software implementations or fail altogether.
- * You cannot use MediaPlayer2
- * after you call {@link #close()}. There is no way to bring it back to any other state.</li>
- *
- * <li>The current playback position can be retrieved with a call to
- * {@link #getCurrentPosition()},
- * which is helpful for applications such as a Music player that need to keep track of the playback
- * progress.</li>
- *
- * <li>The playback position can be adjusted with a call to {@link #seekTo}. Although the
- * asynchronous {@link #seekTo} call returns right away, the actual seek operation may take a
- * while to finish, especially for audio/video being streamed. If you register an
- * {@link EventCallback#onCallCompleted onCallCompleted} <a href="#Callbacks">callback</a>,
- * the callback is
- * called When the seek operation completes with {@link #CALL_COMPLETED_SEEK_TO}.</li>
- *
- * <li>You can call {@link #seekTo} from the <strong>Paused</strong> state.
- * In this case, if you are playing a video stream and
- * the requested position is valid  one video frame is displayed.</li>
- *
- * </ul>
- *
- * <h3 id="InvalidStates">Invalid method calls</h3>
- *
- * <p>The only methods you safely call from the <strong>Error</strong> state are
- * {@link #close},
- * {@link #reset},
- * {@link #notifyWhenCommandLabelReached},
- * {@link #clearPendingCommands},
- * {@link #registerEventCallback},
- * {@link #unregisterEventCallback}
- * and {@link #getState}.
- * Any other methods might throw an exception, return meaningless data, or invoke a
- * {@link EventCallback#onCallCompleted onCallCompleted} with an error code.</p>
- *
- * <p>Most methods can be called from any non-Error state. They will either perform their work or
- * silently have no effect. The following table lists the methods that will invoke a
- * {@link EventCallback#onCallCompleted onCallCompleted} with an error code
- * or throw an exception when they are called from the associated invalid states.</p>
- *
- * <table border="0" cellspacing="0" cellpadding="0">
- * <tr><th>Method Name</th>
- * <th>Invalid States</th></tr>
- *
- * <tr><td>setDataSource</td> <td>{Prepared, Paused, Playing}</td></tr>
- * <tr><td>prepare</td> <td>{Prepared, Paused, Playing}</td></tr>
- * <tr><td>play</td> <td>{Idle}</td></tr>
- * <tr><td>pause</td> <td>{Idle}</td></tr>
- * <tr><td>seekTo</td> <td>{Idle}</td></tr>
- * <tr><td>getCurrentPosition</td> <td>{Idle}</td></tr>
- * <tr><td>getDuration</td> <td>{Idle}</td></tr>
- * <tr><td>getBufferedPosition</td> <td>{Idle}</td></tr>
- * <tr><td>getTrackInfo</td> <td>{Idle}</td></tr>
- * <tr><td>getSelectedTrack</td> <td>{Idle}</td></tr>
- * <tr><td>selectTrack</td> <td>{Idle}</td></tr>
- * <tr><td>deselectTrack</td> <td>{Idle}</td></tr>
- * </table>
- *
- * <h3 id="Permissions">Permissions</h3>
- * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
- * when used with network-based content.
- *
- * <h3 id="Callbacks">Callbacks</h3>
- * <p>Many errors do not result in a transition to the  <strong>Error</strong> state.
- * It is good programming practice to register callback listeners using
- * {@link #registerEventCallback}.
- * You can receive a callback at any time and from any state.</p>
- *
- * <p>If it's important for your app to respond to state changes (for instance, to update the
- * controls on a transport UI), you should register an
- * {@link EventCallback#onCallCompleted onCallCompleted} and
- * detect state change commands by testing the <code>what</code> parameter for a callback from one
- * of the state transition methods: {@link #CALL_COMPLETED_PREPARE}, {@link #CALL_COMPLETED_PLAY},
- * and {@link #CALL_COMPLETED_PAUSE}.
- * Then check the <code>status</code> parameter. The value {@link #CALL_STATUS_NO_ERROR} indicates a
- * successful transition. Any other value will be an error. Call {@link #getState()} to
- * determine the current state. </p>
- *
- * @hide
- */
-public class MediaPlayer2 implements AutoCloseable, AudioRouting {
-    static {
-        System.loadLibrary("media2_jni");
-        native_init();
-    }
-
-    private static native void native_init();
-
-    private static final int NEXT_SOURCE_STATE_ERROR = -1;
-    private static final int NEXT_SOURCE_STATE_INIT = 0;
-    private static final int NEXT_SOURCE_STATE_PREPARING = 1;
-    private static final int NEXT_SOURCE_STATE_PREPARED = 2;
-
-    private static final String TAG = "MediaPlayer2";
-
-    private Context mContext;
-
-    private long mNativeContext;  // accessed by native methods
-    private long mNativeSurfaceTexture;  // accessed by native methods
-    private int mListenerContext;  // accessed by native methods
-    private SurfaceHolder mSurfaceHolder;
-    private PowerManager.WakeLock mWakeLock = null;
-    private boolean mScreenOnWhilePlaying;
-    private boolean mStayAwake;
-
-    private final Object mSrcLock = new Object();
-    //--- guarded by |mSrcLock| start
-    private SourceInfo mCurrentSourceInfo;
-    private final Queue<SourceInfo> mNextSourceInfos = new ConcurrentLinkedQueue<>();
-    //--- guarded by |mSrcLock| end
-    private final AtomicLong mSrcIdGenerator = new AtomicLong(0);
-
-    private volatile float mVolume = 1.0f;
-    private Size mVideoSize = new Size(0, 0);
-
-    private static ExecutorService sDrmThreadPool = Executors.newCachedThreadPool();
-
-    // Creating a dummy audio track, used for keeping session id alive
-    private final Object mSessionIdLock = new Object();
-    @GuardedBy("mSessionIdLock")
-    private AudioTrack mDummyAudioTrack;
-
-    private HandlerThread mHandlerThread;
-    private final TaskHandler mTaskHandler;
-    private final Object mTaskLock = new Object();
-    @GuardedBy("mTaskLock")
-    private final List<Task> mPendingTasks = new LinkedList<>();
-    @GuardedBy("mTaskLock")
-    private Task mCurrentTask;
-    private final AtomicLong mTaskIdGenerator = new AtomicLong(0);
-
-    @GuardedBy("mTaskLock")
-    boolean mIsPreviousCommandSeekTo = false;
-    // |mPreviousSeekPos| and |mPreviousSeekMode| are valid only when |mIsPreviousCommandSeekTo|
-    // is true, and they are accessed on |mHandlerThread| only.
-    long mPreviousSeekPos = -1;
-    int mPreviousSeekMode = SEEK_PREVIOUS_SYNC;
-
-    @GuardedBy("this")
-    private boolean mReleased;
-
-    private final CloseGuard mGuard = CloseGuard.get();
-
-    /**
-     * Default constructor.
-     * <p>When done with the MediaPlayer2, you should call {@link #close()},
-     * to free the resources. If not released, too many MediaPlayer2 instances may
-     * result in an exception.</p>
-     */
-    public MediaPlayer2(@NonNull Context context) {
-        mGuard.open("close");
-
-        mContext = context;
-        mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
-        mHandlerThread.start();
-        Looper looper = mHandlerThread.getLooper();
-        mTaskHandler = new TaskHandler(this, looper);
-        AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        int sessionId = am.generateAudioSessionId();
-        keepAudioSessionIdAlive(sessionId);
-
-        /* Native setup requires a weak reference to our object.
-         * It's easier to create it here than in C++.
-         */
-        native_setup(sessionId, new WeakReference<MediaPlayer2>(this));
-    }
-
-    private native void native_setup(int sessionId, Object mediaplayer2This);
-
-    /**
-     * Releases the resources held by this {@code MediaPlayer2} object.
-     *
-     * It is considered good practice to call this method when you're
-     * done using the MediaPlayer2. In particular, whenever an Activity
-     * of an application is paused (its onPause() method is called),
-     * or stopped (its onStop() method is called), this method should be
-     * invoked to release the MediaPlayer2 object, unless the application
-     * has a special need to keep the object around. In addition to
-     * unnecessary resources (such as memory and instances of codecs)
-     * being held, failure to call this method immediately if a
-     * MediaPlayer2 object is no longer needed may also lead to
-     * continuous battery consumption for mobile devices, and playback
-     * failure for other applications if no multiple instances of the
-     * same codec are supported on a device. Even if multiple instances
-     * of the same codec are supported, some performance degradation
-     * may be expected when unnecessary multiple instances are used
-     * at the same time.
-     *
-     * {@code close()} may be safely called after a prior {@code close()}.
-     * This class implements the Java {@code AutoCloseable} interface and
-     * may be used with try-with-resources.
-     */
-    // This is a synchronous call.
-    @Override
-    public void close() {
-        synchronized (mGuard) {
-            mGuard.close();
-        }
-        release();
-    }
-
-    private synchronized void release() {
-        if (mReleased) {
-            return;
-        }
-        stayAwake(false);
-        updateSurfaceScreenOn();
-        synchronized (mEventCbLock) {
-            mEventCallbackRecords.clear();
-        }
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-            mHandlerThread = null;
-        }
-
-        clearSourceInfos();
-
-        // Modular DRM clean up
-        synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallback = null;
-        }
-        clearMediaDrmObjects();
-
-        native_release();
-
-        synchronized (mSessionIdLock) {
-            mDummyAudioTrack.release();
-        }
-
-        mReleased = true;
-    }
-
-    void clearMediaDrmObjects() {
-        Collection<MediaDrm> drmObjs = mDrmObjs.values();
-        synchronized (mDrmObjs) {
-            for (MediaDrm drmObj : drmObjs) {
-                drmObj.close();
-            }
-            mDrmObjs.clear();
-        }
-    }
-
-    private native void native_release();
-
-    // Have to declare protected for finalize() since it is protected
-    // in the base class Object.
-    @Override
-    protected void finalize() throws Throwable {
-        if (mGuard != null) {
-            mGuard.warnIfOpen();
-        }
-
-        close();
-        native_finalize();
-    }
-
-    private native void native_finalize();
-
-    /**
-     * Resets the MediaPlayer2 to its uninitialized state. After calling
-     * this method, you will have to initialize it again by setting the
-     * data source and calling prepare().
-     */
-    // This is a synchronous call.
-    public void reset() {
-        clearSourceInfos();
-        clearMediaDrmObjects();
-
-        stayAwake(false);
-        native_reset();
-
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        int sessionId = am.generateAudioSessionId();
-        keepAudioSessionIdAlive(sessionId);
-
-        // make sure none of the listeners get called anymore
-        if (mTaskHandler != null) {
-            mTaskHandler.removeCallbacksAndMessages(null);
-        }
-
-    }
-
-    private native void native_reset();
-
-    /**
-     * Starts or resumes playback. If playback had previously been paused,
-     * playback will continue from where it was paused. If playback had
-     * reached end of stream and been paused, or never started before,
-     * playback will start at the beginning.
-     *
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object play() {
-        return addTask(new Task(CALL_COMPLETED_PLAY, false) {
-            @Override
-            void process() {
-                stayAwake(true);
-                native_start();
-            }
-        });
-    }
-
-    private native void native_start() throws IllegalStateException;
-
-    /**
-     * Prepares the player for playback, asynchronously.
-     *
-     * After setting the datasource and the display surface, you need to call prepare().
-     *
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object prepare() {
-        return addTask(new Task(CALL_COMPLETED_PREPARE, true) {
-            @Override
-            void process() {
-                native_prepare();
-            }
-        });
-    }
-
-    private native void native_prepare();
-
-    /**
-     * Pauses playback. Call play() to resume.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object pause() {
-        return addTask(new Task(CALL_COMPLETED_PAUSE, false) {
-            @Override
-            void process() {
-                stayAwake(false);
-
-                native_pause();
-            }
-        });
-    }
-
-    private native void native_pause() throws IllegalStateException;
-
-    /**
-     * Tries to play next data source if applicable.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object skipToNext() {
-        return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) {
-            @Override
-            void process() {
-                if (getState() == PLAYER_STATE_PLAYING) {
-                    native_pause();
-                }
-                playNextDataSource();
-            }
-        });
-    }
-
-    /**
-     * Gets the current playback position.
-     *
-     * @return the current position in milliseconds
-     */
-    public native long getCurrentPosition();
-
-    /**
-     * Gets the duration of the current data source.
-     * Same as {@link #getDuration(DataSourceDesc)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @return the duration in milliseconds, if no duration is available
-     *         (for example, if streaming live content), -1 is returned.
-     * @throws NullPointerException if current data source is null
-     */
-    public long getDuration() {
-        return getDuration(getCurrentDataSource());
-    }
-
-    /**
-     * Gets the duration of the dsd.
-     *
-     * @param dsd the descriptor of data source of which you want to get duration
-     * @return the duration in milliseconds, if no duration is available
-     *         (for example, if streaming live content), -1 is returned.
-     * @throws NullPointerException if dsd is null
-     */
-    public long getDuration(@NonNull DataSourceDesc dsd) {
-        if (dsd == null) {
-            throw new NullPointerException("non-null dsd is expected");
-        }
-        SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo == null) {
-            return -1;
-        }
-
-        return native_getDuration(sourceInfo.mId);
-    }
-
-    private native long native_getDuration(long srcId);
-
-    /**
-     * Gets the buffered media source position of current data source.
-     * Same as {@link #getBufferedPosition(DataSourceDesc)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @return the current buffered media source position in milliseconds
-     * @throws NullPointerException if current data source is null
-     */
-    public long getBufferedPosition() {
-        return getBufferedPosition(getCurrentDataSource());
-    }
-
-    /**
-     * Gets the buffered media source position of given dsd.
-     * For example a buffering update of 8000 milliseconds when 5000 milliseconds of the content
-     * has already been played indicates that the next 3000 milliseconds of the
-     * content to play has been buffered.
-     *
-     * @param dsd the descriptor of data source of which you want to get buffered position
-     * @return the current buffered media source position in milliseconds
-     * @throws NullPointerException if dsd is null
-     */
-    public long getBufferedPosition(@NonNull DataSourceDesc dsd) {
-        if (dsd == null) {
-            throw new NullPointerException("non-null dsd is expected");
-        }
-        SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo == null) {
-            return 0;
-        }
-
-        // Use cached buffered percent for now.
-        int bufferedPercentage = sourceInfo.mBufferedPercentage.get();
-
-        long duration = getDuration(dsd);
-        if (duration < 0) {
-            duration = 0;
-        }
-
-        return duration * bufferedPercentage / 100;
-    }
-
-    /**
-     * MediaPlayer2 has not been prepared or just has been reset.
-     * In this state, MediaPlayer2 doesn't fetch data.
-     */
-    public static final int PLAYER_STATE_IDLE = 1001;
-
-    /**
-     * MediaPlayer2 has been just prepared.
-     * In this state, MediaPlayer2 just fetches data from media source,
-     * but doesn't actively render data.
-     */
-    public static final int PLAYER_STATE_PREPARED = 1002;
-
-    /**
-     * MediaPlayer2 is paused.
-     * In this state, MediaPlayer2 has allocated resources to construct playback
-     * pipeline, but it doesn't actively render data.
-     */
-    public static final int PLAYER_STATE_PAUSED = 1003;
-
-    /**
-     * MediaPlayer2 is actively playing back data.
-     */
-    public static final int PLAYER_STATE_PLAYING = 1004;
-
-    /**
-     * MediaPlayer2 has hit some fatal error and cannot continue playback.
-     */
-    public static final int PLAYER_STATE_ERROR = 1005;
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "MEDIAPLAYER2_STATE", value = {
-        PLAYER_STATE_IDLE,
-        PLAYER_STATE_PREPARED,
-        PLAYER_STATE_PAUSED,
-        PLAYER_STATE_PLAYING,
-        PLAYER_STATE_ERROR })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaPlayer2State {}
-
-    /**
-     * Gets the current player state.
-     *
-     * @return the current player state.
-     */
-    public @MediaPlayer2State int getState() {
-        return native_getState();
-    }
-
-    private native int native_getState();
-
-    /**
-     * Sets the audio attributes for this MediaPlayer2.
-     * See {@link AudioAttributes} for how to build and configure an instance of this class.
-     * You must call this method before {@link #play()} and {@link #pause()} in order
-     * for the audio attributes to become effective thereafter.
-     * @param attributes a non-null set of audio attributes
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setAudioAttributes(@NonNull AudioAttributes attributes) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) {
-            @Override
-            void process() {
-                if (attributes == null) {
-                    final String msg = "Cannot set AudioAttributes to null";
-                    throw new IllegalArgumentException(msg);
-                }
-                native_setAudioAttributes(attributes);
-            }
-        });
-    }
-
-    // return true if the parameter is set successfully, false otherwise
-    private native boolean native_setAudioAttributes(AudioAttributes audioAttributes);
-
-    /**
-     * Gets the audio attributes for this MediaPlayer2.
-     * @return attributes a set of audio attributes
-     */
-    public @NonNull AudioAttributes getAudioAttributes() {
-        return native_getAudioAttributes();
-    }
-
-    private native AudioAttributes native_getAudioAttributes();
-
-    /**
-     * Sets the data source as described by a DataSourceDesc.
-     * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
-     * in the {@link FileDataSourceDesc} will be closed by the player.
-     *
-     * @param dsd the descriptor of data source you want to play
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setDataSource(@NonNull DataSourceDesc dsd) {
-        return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
-            @Override
-            void process() throws IOException {
-                checkDataSourceDesc(dsd);
-                int state = getState();
-                try {
-                    if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
-                        throw new IllegalStateException("called in wrong state " + state);
-                    }
-
-                    synchronized (mSrcLock) {
-                        setCurrentSourceInfo_l(new SourceInfo(dsd));
-                        handleDataSource(true /* isCurrent */, dsd, mCurrentSourceInfo.mId);
-                    }
-                } finally {
-                    dsd.close();
-                }
-            }
-
-        });
-    }
-
-    /**
-     * Sets a single data source as described by a DataSourceDesc which will be played
-     * after current data source is finished.
-     * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
-     * in the {@link FileDataSourceDesc} will be closed by the player.
-     *
-     * @param dsd the descriptor of data source you want to play after current one
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setNextDataSource(@NonNull DataSourceDesc dsd) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
-            @Override
-            void process() {
-                checkDataSourceDesc(dsd);
-                synchronized (mSrcLock) {
-                    clearNextSourceInfos_l();
-                    mNextSourceInfos.add(new SourceInfo(dsd));
-                }
-                prepareNextDataSource();
-            }
-        });
-    }
-
-    /**
-     * Sets a list of data sources to be played sequentially after current data source is done.
-     * When the data source is of {@link FileDataSourceDesc} type, the {@link ParcelFileDescriptor}
-     * in the {@link FileDataSourceDesc} will be closed by the player.
-     *
-     * @param dsds the list of data sources you want to play after current one
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) {
-        return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) {
-            @Override
-            void process() {
-                if (dsds == null || dsds.size() == 0) {
-                    throw new IllegalArgumentException("data source list cannot be null or empty.");
-                }
-                boolean hasError = false;
-                for (DataSourceDesc dsd : dsds) {
-                    if (dsd == null) {
-                        hasError = true;
-                        continue;
-                    }
-                    if (dsd instanceof FileDataSourceDesc) {
-                        FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
-                        if (fdsd.isPFDClosed()) {
-                            hasError = true;
-                            continue;
-                        }
-
-                        fdsd.incCount();
-                    }
-                }
-                if (hasError) {
-                    for (DataSourceDesc dsd : dsds) {
-                        if (dsd != null) {
-                            dsd.close();
-                        }
-                    }
-                    throw new IllegalArgumentException("invalid data source list");
-                }
-
-                synchronized (mSrcLock) {
-                    clearNextSourceInfos_l();
-                    for (DataSourceDesc dsd : dsds) {
-                        mNextSourceInfos.add(new SourceInfo(dsd));
-                    }
-                }
-                prepareNextDataSource();
-            }
-        });
-    }
-
-    // throws IllegalArgumentException if dsd is null or underline PFD of dsd has been closed.
-    private void checkDataSourceDesc(DataSourceDesc dsd) {
-        if (dsd == null) {
-            throw new IllegalArgumentException("dsd is expected to be non null");
-        }
-        if (dsd instanceof FileDataSourceDesc) {
-            FileDataSourceDesc fdsd = (FileDataSourceDesc) dsd;
-            if (fdsd.isPFDClosed()) {
-                throw new IllegalArgumentException("the underline FileDescriptor has been closed");
-            }
-            fdsd.incCount();
-        }
-    }
-
-    /**
-     * Removes all data sources pending to be played.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object clearNextDataSources() {
-        return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) {
-            @Override
-            void process() {
-                synchronized (mSrcLock) {
-                    clearNextSourceInfos_l();
-                }
-            }
-        });
-    }
-
-    /**
-     * Gets the current data source as described by a DataSourceDesc.
-     *
-     * @return the current DataSourceDesc
-     */
-    public @Nullable DataSourceDesc getCurrentDataSource() {
-        synchronized (mSrcLock) {
-            return mCurrentSourceInfo == null ? null : mCurrentSourceInfo.mDSD;
-        }
-    }
-
-    private void handleDataSource(boolean isCurrent, @NonNull DataSourceDesc dsd, long srcId)
-            throws IOException {
-        Media2Utils.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
-
-        if (dsd instanceof FileDataSourceDesc) {
-            FileDataSourceDesc fileDSD = (FileDataSourceDesc) dsd;
-            ParcelFileDescriptor pfd = fileDSD.getParcelFileDescriptor();
-            if (pfd.getStatSize() == -1) {
-                // Underlying pipeline doesn't understand '-1' size. Create a wrapper for
-                // translation.
-                // TODO: Make native code handle '-1' size.
-                handleDataSource(isCurrent,
-                        srcId,
-                        new ProxyDataSourceCallback(pfd),
-                        fileDSD.getStartPosition(),
-                        fileDSD.getEndPosition());
-            } else {
-                handleDataSource(isCurrent,
-                        srcId,
-                        pfd,
-                        fileDSD.getOffset(),
-                        fileDSD.getLength(),
-                        fileDSD.getStartPosition(),
-                        fileDSD.getEndPosition());
-            }
-        } else if (dsd instanceof UriDataSourceDesc) {
-            UriDataSourceDesc uriDSD = (UriDataSourceDesc) dsd;
-            handleDataSource(isCurrent,
-                             srcId,
-                             mContext,
-                             uriDSD.getUri(),
-                             uriDSD.getHeaders(),
-                             uriDSD.getCookies(),
-                             uriDSD.getStartPosition(),
-                             uriDSD.getEndPosition());
-        } else {
-            throw new IllegalArgumentException("Unsupported DataSourceDesc. " + dsd.toString());
-        }
-    }
-
-    /**
-     * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
-     * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
-     * this API to pass the cookies as a list of HttpCookie. If the app has not installed
-     * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
-     * the provided cookies. If the app has installed its own handler already, this API requires the
-     * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
-     *
-     * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
-     * but that can be changed with key/value pairs through the headers parameter with
-     * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
-     * disallow or allow cross domain redirection.
-     *
-     * @throws IllegalArgumentException if cookies are provided and the installed handler is not
-     *                                  a CookieManager
-     * @throws IllegalStateException    if it is called in an invalid state
-     * @throws NullPointerException     if context or uri is null
-     * @throws IOException              if uri has a file scheme and an I/O error occurs
-     */
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            @NonNull Context context, @NonNull Uri uri,
-            @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        // The context and URI usually belong to the calling user. Get a resolver for that user.
-        final ContentResolver resolver = context.getContentResolver();
-        final String scheme = uri.getScheme();
-        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
-            handleDataSource(isCurrent, srcId, uri.getPath(), null, null, startPos, endPos);
-            return;
-        }
-
-        final int ringToneType = RingtoneManager.getDefaultType(uri);
-        try {
-            AssetFileDescriptor afd;
-            // Try requested Uri locally first
-            if (ContentResolver.SCHEME_CONTENT.equals(scheme) && ringToneType != -1) {
-                afd = RingtoneManager.openDefaultRingtoneUri(context, uri);
-                if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
-                    return;
-                }
-                final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(
-                        context, ringToneType);
-                afd = resolver.openAssetFileDescriptor(actualUri, "r");
-            } else {
-                afd = resolver.openAssetFileDescriptor(uri, "r");
-            }
-            if (attemptDataSource(isCurrent, srcId, afd, startPos, endPos)) {
-                return;
-            }
-        } catch (NullPointerException | SecurityException | IOException ex) {
-            Log.w(TAG, "Couldn't open " + uri == null ? "null uri" : uri.toSafeString(), ex);
-            // Fallback to media server
-        }
-        handleDataSource(isCurrent, srcId, uri.toString(), headers, cookies, startPos, endPos);
-    }
-
-    private boolean attemptDataSource(boolean isCurrent, long srcId, AssetFileDescriptor afd,
-            long startPos, long endPos) throws IOException {
-        try {
-            if (afd.getDeclaredLength() < 0) {
-                handleDataSource(isCurrent,
-                        srcId,
-                        ParcelFileDescriptor.dup(afd.getFileDescriptor()),
-                        0,
-                        DataSourceDesc.LONG_MAX,
-                        startPos,
-                        endPos);
-            } else {
-                handleDataSource(isCurrent,
-                        srcId,
-                        ParcelFileDescriptor.dup(afd.getFileDescriptor()),
-                        afd.getStartOffset(),
-                        afd.getDeclaredLength(),
-                        startPos,
-                        endPos);
-            }
-            return true;
-        } catch (NullPointerException | SecurityException | IOException ex) {
-            Log.w(TAG, "Couldn't open srcId:" + srcId + ": " + ex);
-            return false;
-        } finally {
-            if (afd != null) {
-                afd.close();
-            }
-        }
-    }
-
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            String path, Map<String, String> headers, List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        String[] keys = null;
-        String[] values = null;
-
-        if (headers != null) {
-            keys = new String[headers.size()];
-            values = new String[headers.size()];
-
-            int i = 0;
-            for (Map.Entry<String, String> entry: headers.entrySet()) {
-                keys[i] = entry.getKey();
-                values[i] = entry.getValue();
-                ++i;
-            }
-        }
-        handleDataSource(isCurrent, srcId, path, keys, values, cookies, startPos, endPos);
-    }
-
-    private void handleDataSource(boolean isCurrent, long srcId,
-            String path, String[] keys, String[] values, List<HttpCookie> cookies,
-            long startPos, long endPos)
-            throws IOException {
-        final Uri uri = Uri.parse(path);
-        final String scheme = uri.getScheme();
-        if ("file".equals(scheme)) {
-            path = uri.getPath();
-        } else if (scheme != null) {
-            // handle non-file sources
-            Media2Utils.storeCookies(cookies);
-            nativeHandleDataSourceUrl(
-                    isCurrent,
-                    srcId,
-                    Media2HTTPService.createHTTPService(path),
-                    path,
-                    keys,
-                    values,
-                    startPos,
-                    endPos);
-            return;
-        }
-
-        final File file = new File(path);
-        if (file.exists()) {
-            FileInputStream is = new FileInputStream(file);
-            FileDescriptor fd = is.getFD();
-            handleDataSource(isCurrent, srcId, ParcelFileDescriptor.dup(fd),
-                    0, DataSourceDesc.LONG_MAX, startPos, endPos);
-            is.close();
-        } else {
-            throw new IOException("handleDataSource failed.");
-        }
-    }
-
-    private native void nativeHandleDataSourceUrl(
-            boolean isCurrent, long srcId,
-            Media2HTTPService httpService, String path, String[] keys, String[] values,
-            long startPos, long endPos)
-            throws IOException;
-
-    /**
-     * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
-     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
-     * to close the file descriptor. It is safe to do so as soon as this call returns.
-     *
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws IllegalArgumentException if fd is not a valid FileDescriptor
-     * @throws IOException if fd can not be read
-     */
-    private void handleDataSource(
-            boolean isCurrent, long srcId,
-            ParcelFileDescriptor pfd, long offset, long length,
-            long startPos, long endPos) throws IOException {
-        nativeHandleDataSourceFD(isCurrent, srcId, pfd.getFileDescriptor(), offset, length,
-                startPos, endPos);
-    }
-
-    private native void nativeHandleDataSourceFD(boolean isCurrent, long srcId,
-            FileDescriptor fd, long offset, long length,
-            long startPos, long endPos) throws IOException;
-
-    /**
-     * @throws IllegalStateException if it is called in an invalid state
-     * @throws IllegalArgumentException if dataSource is not a valid DataSourceCallback
-     */
-    private void handleDataSource(boolean isCurrent, long srcId, DataSourceCallback dataSource,
-            long startPos, long endPos) {
-        nativeHandleDataSourceCallback(isCurrent, srcId, dataSource, startPos, endPos);
-    }
-
-    private native void nativeHandleDataSourceCallback(
-            boolean isCurrent, long srcId, DataSourceCallback dataSource,
-            long startPos, long endPos);
-
-    // return true if there is a next data source, false otherwise.
-    // This function should be always called on |mHandlerThread|.
-    private boolean prepareNextDataSource() {
-        HandlerThread handlerThread = mHandlerThread;
-        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
-            Log.e(TAG, "prepareNextDataSource: called on wrong looper");
-        }
-
-        boolean hasNextDSD;
-        int state = getState();
-        synchronized (mSrcLock) {
-            hasNextDSD = !mNextSourceInfos.isEmpty();
-            if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) {
-                // Current source has not been prepared yet.
-                return hasNextDSD;
-            }
-
-            SourceInfo nextSource = mNextSourceInfos.peek();
-            if (!hasNextDSD || nextSource.mStateAsNextSource != NEXT_SOURCE_STATE_INIT) {
-                // There is no next source or it's in preparing or prepared state.
-                return hasNextDSD;
-            }
-
-            try {
-                nextSource.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARING;
-                handleDataSource(false /* isCurrent */, nextSource.mDSD, nextSource.mId);
-            } catch (Exception e) {
-                Message msg = mTaskHandler.obtainMessage(
-                        MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null);
-                mTaskHandler.handleMessage(msg, nextSource.mId);
-
-                SourceInfo nextSourceInfo = mNextSourceInfos.poll();
-                if (nextSource != null) {
-                    nextSourceInfo.close();
-                }
-                return prepareNextDataSource();
-            }
-        }
-        return hasNextDSD;
-    }
-
-    // This function should be always called on |mHandlerThread|.
-    private void playNextDataSource() {
-        HandlerThread handlerThread = mHandlerThread;
-        if (handlerThread != null && Looper.myLooper() != handlerThread.getLooper()) {
-            Log.e(TAG, "playNextDataSource: called on wrong looper");
-        }
-
-        boolean hasNextDSD = false;
-        synchronized (mSrcLock) {
-            if (!mNextSourceInfos.isEmpty()) {
-                hasNextDSD = true;
-                SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_PREPARED) {
-                    // Switch to next source only when it has been prepared.
-                    setCurrentSourceInfo_l(mNextSourceInfos.poll());
-
-                    long srcId = mCurrentSourceInfo.mId;
-                    try {
-                        nativePlayNextDataSource(srcId);
-                    } catch (Exception e) {
-                        Message msg2 = mTaskHandler.obtainMessage(
-                                MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
-                        mTaskHandler.handleMessage(msg2, srcId);
-                        // Keep |mNextSourcePlayPending|
-                        hasNextDSD = prepareNextDataSource();
-                    }
-                    if (hasNextDSD) {
-                        stayAwake(true);
-
-                        // Now a new current src is playing.
-                        // Wait for MEDIA_INFO_DATA_SOURCE_START to prepare next source.
-                    }
-                } else if (nextSourceInfo.mStateAsNextSource == NEXT_SOURCE_STATE_INIT) {
-                    hasNextDSD = prepareNextDataSource();
-                }
-            }
-        }
-
-        if (!hasNextDSD) {
-            sendEvent(new EventNotifier() {
-                @Override
-                public void notify(EventCallback callback) {
-                    callback.onInfo(
-                            MediaPlayer2.this, null, MEDIA_INFO_DATA_SOURCE_LIST_END, 0);
-                }
-            });
-        }
-    }
-
-    private native void nativePlayNextDataSource(long srcId);
-
-    /**
-     * Configures the player to loop on the current data source.
-     * @param loop true if the current data source is meant to loop.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object loopCurrent(boolean loop) {
-        return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) {
-            @Override
-            void process() {
-                setLooping(loop);
-            }
-        });
-    }
-
-    private native void setLooping(boolean looping);
-
-    /**
-     * Sets the volume of the audio of the media to play, expressed as a linear multiplier
-     * on the audio samples.
-     * Note that this volume is specific to the player, and is separate from stream volume
-     * used across the platform.<br>
-     * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
-     * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
-     * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setPlayerVolume(float volume) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) {
-            @Override
-            void process() {
-                mVolume = volume;
-                native_setVolume(volume);
-            }
-        });
-    }
-
-    private native void native_setVolume(float volume);
-
-    /**
-     * Returns the current volume of this player.
-     * Note that it does not take into account the associated stream volume.
-     * @return the player volume.
-     */
-    public float getPlayerVolume() {
-        return mVolume;
-    }
-
-    /**
-     * @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
-     */
-    public float getMaxPlayerVolume() {
-        return 1.0f;
-    }
-
-    /**
-     * Insert a task in the command queue to help the client to identify whether a batch
-     * of commands has been finished. When this command is processed, a notification
-     * {@link EventCallback#onCommandLabelReached onCommandLabelReached} will be fired with the
-     * given {@code label}.
-     *
-     * @see EventCallback#onCommandLabelReached
-     *
-     * @param label An application specific Object used to help to identify the completeness
-     * of a batch of commands.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object notifyWhenCommandLabelReached(@NonNull Object label) {
-        return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
-            @Override
-            void process() {
-                sendEvent(new EventNotifier() {
-                    @Override
-                    public void notify(EventCallback callback) {
-                        callback.onCommandLabelReached(
-                                MediaPlayer2.this, label);
-                    }
-                });
-            }
-        });
-    }
-
-    /**
-     * Sets the {@link SurfaceHolder} to use for displaying the video
-     * portion of the media.
-     *
-     * Either a surface holder or surface must be set if a display or video sink
-     * is needed. Not calling this method or {@link #setSurface(Surface)}
-     * when playing back a video will result in only the audio track being played.
-     * A null surface holder or surface will result in only the audio track being
-     * played.
-     *
-     * @param sh the SurfaceHolder to use for video display
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    public @NonNull Object setDisplay(@Nullable SurfaceHolder sh) {
-        return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) {
-            @Override
-            void process() {
-                mSurfaceHolder = sh;
-                Surface surface;
-                if (sh != null) {
-                    surface = sh.getSurface();
-                } else {
-                    surface = null;
-                }
-                native_setVideoSurface(surface);
-                updateSurfaceScreenOn();
-            }
-        });
-    }
-
-    /**
-     * Sets the {@link Surface} to be used as the sink for the video portion of
-     * the media.  Setting a
-     * Surface will un-set any Surface or SurfaceHolder that was previously set.
-     * A null surface will result in only the audio track being played.
-     *
-     * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
-     * returned from {@link SurfaceTexture#getTimestamp()} will have an
-     * unspecified zero point.  These timestamps cannot be directly compared
-     * between different media sources, different instances of the same media
-     * source, or multiple runs of the same program.  The timestamp is normally
-     * monotonically increasing and is unaffected by time-of-day adjustments,
-     * but it is reset when the position is set.
-     *
-     * @param surface The {@link Surface} to be used for the video portion of
-     * the media.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setSurface(@Nullable Surface surface) {
-        return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) {
-            @Override
-            void process() {
-                if (mScreenOnWhilePlaying && surface != null) {
-                    Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
-                }
-                mSurfaceHolder = null;
-                native_setVideoSurface(surface);
-                updateSurfaceScreenOn();
-            }
-        });
-    }
-
-    private native void native_setVideoSurface(Surface surface);
-
-    /**
-     * Set the low-level power management behavior for this MediaPlayer2. This
-     * can be used when the MediaPlayer2 is not playing through a SurfaceHolder
-     * set with {@link #setDisplay(SurfaceHolder)} and thus can use the
-     * high-level {@link #setScreenOnWhilePlaying(boolean)} feature.
-     *
-     * <p>This function has the MediaPlayer2 access the low-level power manager
-     * service to control the device's power usage while playing is occurring.
-     * The parameter is a {@link android.os.PowerManager.WakeLock}.
-     * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
-     * permission.
-     * By default, no attempt is made to keep the device awake during playback.
-     *
-     * @param wakeLock the power wake lock used during playback.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     * @see android.os.PowerManager
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setWakeLock(@NonNull PowerManager.WakeLock wakeLock) {
-        return addTask(new Task(CALL_COMPLETED_SET_WAKE_LOCK, false) {
-            @Override
-            void process() {
-                boolean wasHeld = false;
-
-                if (mWakeLock != null) {
-                    if (mWakeLock.isHeld()) {
-                        wasHeld = true;
-                        mWakeLock.release();
-                    }
-                }
-
-                mWakeLock = wakeLock;
-                if (mWakeLock != null) {
-                    mWakeLock.setReferenceCounted(false);
-                    if (wasHeld) {
-                        mWakeLock.acquire();
-                    }
-                }
-            }
-        });
-    }
-
-    /**
-     * Control whether we should use the attached SurfaceHolder to keep the
-     * screen on while video playback is occurring.  This is the preferred
-     * method over {@link #setWakeLock} where possible, since it doesn't
-     * require that the application have permission for low-level wake lock
-     * access.
-     *
-     * @param screenOn Supply true to keep the screen on, false to allow it to turn off.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setScreenOnWhilePlaying(boolean screenOn) {
-        return addTask(new Task(CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING, false) {
-            @Override
-            void process() {
-                if (mScreenOnWhilePlaying != screenOn) {
-                    if (screenOn && mSurfaceHolder == null) {
-                        Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective"
-                                + " without a SurfaceHolder");
-                    }
-                    mScreenOnWhilePlaying = screenOn;
-                    updateSurfaceScreenOn();
-                }
-            }
-        });
-    }
-
-    private void stayAwake(boolean awake) {
-        if (mWakeLock != null) {
-            if (awake && !mWakeLock.isHeld()) {
-                mWakeLock.acquire();
-            } else if (!awake && mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
-        }
-        mStayAwake = awake;
-        updateSurfaceScreenOn();
-    }
-
-    private void updateSurfaceScreenOn() {
-        if (mSurfaceHolder != null) {
-            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
-        }
-    }
-
-    /**
-     * Cancels a pending command.
-     *
-     * @param token the command to be canceled. This is the returned Object when command is issued.
-     * @return {@code false} if the task could not be cancelled; {@code true} otherwise.
-     * @throws IllegalArgumentException if argument token is null.
-     */
-    // This is a synchronous call.
-    public boolean cancelCommand(@NonNull Object token) {
-        if (token == null) {
-            throw new IllegalArgumentException("command token should not be null");
-        }
-        synchronized (mTaskLock) {
-            return mPendingTasks.remove(token);
-        }
-    }
-
-    /**
-     * Discards all pending commands.
-     */
-    // This is a synchronous call.
-    public void clearPendingCommands() {
-        synchronized (mTaskLock) {
-            mPendingTasks.clear();
-        }
-    }
-
-    //--------------------------------------------------------------------------
-    // Explicit Routing
-    //--------------------
-    private AudioDeviceInfo mPreferredDevice = null;
-
-    /**
-     * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
-     * the output from this MediaPlayer2.
-     * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
-     *  If deviceInfo is null, default routing is restored.
-     * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
-     * does not correspond to a valid audio device.
-     */
-    // This is a synchronous call.
-    @Override
-    public boolean setPreferredDevice(@Nullable AudioDeviceInfo deviceInfo) {
-        boolean status = native_setPreferredDevice(deviceInfo);
-        if (status) {
-            synchronized (this) {
-                mPreferredDevice = deviceInfo;
-            }
-        }
-        return status;
-    }
-
-    private native boolean native_setPreferredDevice(AudioDeviceInfo device);
-
-    /**
-     * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
-     * is not guaranteed to correspond to the actual device being used for playback.
-     */
-    @Override
-    public @Nullable AudioDeviceInfo getPreferredDevice() {
-        synchronized (this) {
-            return mPreferredDevice;
-        }
-    }
-
-    /**
-     * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
-     * Note: The query is only valid if the MediaPlayer2 is currently playing.
-     * If the player is not playing, the returned device can be null or correspond to previously
-     * selected device when the player was last active.
-     */
-    @Override
-    public @Nullable native AudioDeviceInfo getRoutedDevice();
-
-    /**
-     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
-     * changes on this MediaPlayer2.
-     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
-     * notifications of rerouting events.
-     * @param handler  Specifies the {@link Handler} object for the thread on which to execute
-     * the callback. If <code>null</code>, the handler on the main looper will be used.
-     */
-    // This is a synchronous call.
-    @Override
-    public void addOnRoutingChangedListener(@NonNull AudioRouting.OnRoutingChangedListener listener,
-            @Nullable Handler handler) {
-        if (listener == null) {
-            throw new IllegalArgumentException("addOnRoutingChangedListener: listener is NULL");
-        }
-        RoutingDelegate routingDelegate = new RoutingDelegate(this, listener, handler);
-        native_addDeviceCallback(routingDelegate);
-    }
-
-    private native void native_addDeviceCallback(RoutingDelegate rd);
-
-    /**
-     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
-     * to receive rerouting notifications.
-     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
-     * to remove.
-     */
-    // This is a synchronous call.
-    @Override
-    public void removeOnRoutingChangedListener(
-            @NonNull AudioRouting.OnRoutingChangedListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("removeOnRoutingChangedListener: listener is NULL");
-        }
-        native_removeDeviceCallback(listener);
-    }
-
-    private native void native_removeDeviceCallback(
-            AudioRouting.OnRoutingChangedListener listener);
-
-    /**
-     * Returns the size of the video.
-     *
-     * @return the size of the video. The width and height of size could be 0 if there is no video,
-     * or the size has not been determined yet.
-     * The {@code EventCallback} can be registered via
-     * {@link #registerEventCallback(Executor, EventCallback)} to provide a
-     * notification {@code EventCallback.onVideoSizeChanged} when the size
-     * is available.
-     */
-    public @NonNull Size getVideoSize() {
-        return mVideoSize;
-    }
-
-    /**
-     * Return Metrics data about the current player.
-     *
-     * @return a {@link PersistableBundle} containing the set of attributes and values
-     * available for the media being handled by this instance of MediaPlayer2
-     * The attributes are descibed in {@link MetricsConstants}.
-     *
-     * Additional vendor-specific fields may also be present in the return value.
-     */
-    public @Nullable PersistableBundle getMetrics() {
-        PersistableBundle bundle = native_getMetrics();
-        return bundle;
-    }
-
-    private native PersistableBundle native_getMetrics();
-
-    /**
-     * Gets the current buffering management params used by the source component.
-     * Calling it only after {@code setDataSource} has been called.
-     * Each type of data source might have different set of default params.
-     *
-     * @return the current buffering management params used by the source component.
-     * @throws IllegalStateException if the internal player engine has not been
-     * initialized, or {@code setDataSource} has not been called.
-     */
-    // TODO: make it public when ready
-    @NonNull
-    native BufferingParams getBufferingParams();
-
-    /**
-     * Sets buffering management params.
-     * The object sets its internal BufferingParams to the input, except that the input is
-     * invalid or not supported.
-     * Call it only after {@code setDataSource} has been called.
-     * The input is a hint to MediaPlayer2.
-     *
-     * @param params the buffering management params.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // TODO: make it public when ready
-    // This is an asynchronous call.
-    @NonNull Object setBufferingParams(@NonNull BufferingParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
-            @Override
-            void process() {
-                Media2Utils.checkArgument(params != null, "the BufferingParams cannot be null");
-                native_setBufferingParams(params);
-            }
-        });
-    }
-
-    private native void native_setBufferingParams(@NonNull BufferingParams params);
-
-    /**
-     * Sets playback rate using {@link PlaybackParams}. The object sets its internal
-     * PlaybackParams to the input. This allows the object to resume at previous speed
-     * when play() is called. Speed of zero is not allowed. Calling it does not change
-     * the object state.
-     *
-     * @param params the playback params.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setPlaybackParams(@NonNull PlaybackParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
-            @Override
-            void process() {
-                Media2Utils.checkArgument(params != null, "the PlaybackParams cannot be null");
-                native_setPlaybackParams(params);
-            }
-        });
-    }
-
-    private native void native_setPlaybackParams(@NonNull PlaybackParams params);
-
-    /**
-     * Gets the playback params, containing the current playback rate.
-     *
-     * @return the playback params.
-     * @throws IllegalStateException if the internal player engine has not been initialized.
-     */
-    @NonNull
-    public native PlaybackParams getPlaybackParams();
-
-    /**
-     * Sets A/V sync mode.
-     *
-     * @param params the A/V sync params to apply
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setSyncParams(@NonNull SyncParams params) {
-        return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
-            @Override
-            void process() {
-                Media2Utils.checkArgument(params != null, "the SyncParams cannot be null");
-                native_setSyncParams(params);
-            }
-        });
-    }
-
-    private native void native_setSyncParams(@NonNull SyncParams params);
-
-    /**
-     * Gets the A/V sync mode.
-     *
-     * @return the A/V sync params
-     * @throws IllegalStateException if the internal player engine has not been initialized.
-     */
-    @NonNull
-    public native SyncParams getSyncParams();
-
-    /**
-     * Moves the media to specified time position.
-     * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}.
-     *
-     * @param msec the offset in milliseconds from the start to seek to
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object seekTo(long msec) {
-        return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
-    }
-
-    /**
-     * Seek modes used in method seekTo(long, int) to move media position
-     * to a specified location.
-     *
-     * Do not change these mode values without updating their counterparts
-     * in include/media/IMediaSource.h!
-     */
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a data source that is located
-     * right before or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_PREVIOUS_SYNC    = 0x00;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a data source that is located
-     * right after or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_NEXT_SYNC        = 0x01;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a sync (or key) frame associated with a data source that is located
-     * closest to (in time) or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST_SYNC     = 0x02;
-    /**
-     * This mode is used with {@link #seekTo(long, int)} to move media position to
-     * a frame (not necessarily a key frame) associated with a data source that
-     * is located closest to or at the given time.
-     *
-     * @see #seekTo(long, int)
-     */
-    public static final int SEEK_CLOSEST          = 0x03;
-
-    /** @hide */
-    @IntDef(flag = false, prefix = "SEEK", value = {
-            SEEK_PREVIOUS_SYNC,
-            SEEK_NEXT_SYNC,
-            SEEK_CLOSEST_SYNC,
-            SEEK_CLOSEST,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SeekMode {}
-
-    /**
-     * Moves the media to specified time position by considering the given mode.
-     * <p>
-     * When seekTo is finished, the user will be notified via
-     * {@link EventCallback#onCallCompleted} with {@link #CALL_COMPLETED_SEEK_TO}.
-     * There is at most one active seekTo processed at any time. If there is a to-be-completed
-     * seekTo, new seekTo requests will be queued in such a way that only the last request
-     * is kept. When current seekTo is completed, the queued request will be processed if
-     * that request is different from just-finished seekTo operation, i.e., the requested
-     * position or mode is different.
-     *
-     * @param msec the offset in milliseconds from the start to seek to.
-     * When seeking to the given time position, there is no guarantee that the data source
-     * has a frame located at the position. When this happens, a frame nearby will be rendered.
-     * If msec is negative, time position zero will be used.
-     * If msec is larger than duration, duration will be used.
-     * @param mode the mode indicating where exactly to seek to.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object seekTo(long msec, @SeekMode int mode) {
-        return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) {
-            @Override
-            void process() {
-                if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
-                    final String msg = "Illegal seek mode: " + mode;
-                    throw new IllegalArgumentException(msg);
-                }
-                // TODO: pass long to native, instead of truncating here.
-                long posMs = msec;
-                if (posMs > Integer.MAX_VALUE) {
-                    Log.w(TAG, "seekTo offset " + posMs + " is too large, cap to "
-                            + Integer.MAX_VALUE);
-                    posMs = Integer.MAX_VALUE;
-                } else if (posMs < Integer.MIN_VALUE) {
-                    Log.w(TAG, "seekTo offset " + posMs + " is too small, cap to "
-                            + Integer.MIN_VALUE);
-                    posMs = Integer.MIN_VALUE;
-                }
-
-                synchronized (mTaskLock) {
-                    if (mIsPreviousCommandSeekTo
-                            && mPreviousSeekPos == posMs
-                            && mPreviousSeekMode == mode) {
-                        throw new CommandSkippedException(
-                                "same as previous seekTo");
-                    }
-                }
-
-                native_seekTo(posMs, mode);
-
-                synchronized (mTaskLock) {
-                    mIsPreviousCommandSeekTo = true;
-                    mPreviousSeekPos = posMs;
-                    mPreviousSeekMode = mode;
-                }
-            }
-        });
-    }
-
-    private native void native_seekTo(long msec, int mode);
-
-    /**
-     * Get current playback position as a {@link MediaTimestamp}.
-     * <p>
-     * The MediaTimestamp represents how the media time correlates to the system time in
-     * a linear fashion using an anchor and a clock rate. During regular playback, the media
-     * time moves fairly constantly (though the anchor frame may be rebased to a current
-     * system time, the linear correlation stays steady). Therefore, this method does not
-     * need to be called often.
-     * <p>
-     * To help users get current playback position, this method always anchors the timestamp
-     * to the current {@link System#nanoTime system time}, so
-     * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
-     *
-     * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
-     *         is available, e.g. because the media player has not been initialized.
-     *
-     * @see MediaTimestamp
-     */
-    @Nullable
-    public MediaTimestamp getTimestamp() {
-        try {
-            // TODO: get the timestamp from native side
-            return new MediaTimestamp(
-                    getCurrentPosition() * 1000L,
-                    System.nanoTime(),
-                    getState() == PLAYER_STATE_PLAYING ? getPlaybackParams().getSpeed() : 0.f);
-        } catch (IllegalStateException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Checks whether the MediaPlayer2 is looping or non-looping.
-     *
-     * @return true if the MediaPlayer2 is currently looping, false otherwise
-     */
-    // This is a synchronous call.
-    public native boolean isLooping();
-
-    /**
-     * Sets the audio session ID.
-     *
-     * @param sessionId the audio session ID.
-     * The audio session ID is a system wide unique identifier for the audio stream played by
-     * this MediaPlayer2 instance.
-     * The primary use of the audio session ID  is to associate audio effects to a particular
-     * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
-     * this effect will be applied only to the audio content of media players within the same
-     * audio session and not to the output mix.
-     * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
-     * However, it is possible to force this player to be part of an already existing audio session
-     * by calling this method.
-     * This method must be called when player is in {@link #PLAYER_STATE_IDLE} or
-     * {@link #PLAYER_STATE_PREPARED} state in order to have sessionId take effect.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setAudioSessionId(int sessionId) {
-        final AudioTrack dummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
-                    AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
-                    AudioTrack.MODE_STATIC, sessionId);
-        return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) {
-            @Override
-            void process() {
-                keepAudioSessionIdAlive(dummyAudioTrack);
-                native_setAudioSessionId(sessionId);
-            }
-        });
-    }
-
-    private native void native_setAudioSessionId(int sessionId);
-
-    /**
-     * Returns the audio session ID.
-     *
-     * @return the audio session ID. {@see #setAudioSessionId(int)}
-     * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
-     * contructed.
-     */
-    // This is a synchronous call.
-    public native int getAudioSessionId();
-
-    /**
-     * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
-     * effect which can be applied on any sound source that directs a certain amount of its
-     * energy to this effect. This amount is defined by setAuxEffectSendLevel().
-     * See {@link #setAuxEffectSendLevel(float)}.
-     * <p>After creating an auxiliary effect (e.g.
-     * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
-     * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
-     * to attach the player to the effect.
-     * <p>To detach the effect from the player, call this method with a null effect id.
-     * <p>This method must be called after one of the overloaded <code> setDataSource </code>
-     * methods.
-     * @param effectId system wide unique id of the effect to attach
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object attachAuxEffect(int effectId) {
-        return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) {
-            @Override
-            void process() {
-                native_attachAuxEffect(effectId);
-            }
-        });
-    }
-
-    private native void native_attachAuxEffect(int effectId);
-
-    /**
-     * Sets the send level of the player to the attached auxiliary effect.
-     * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
-     * <p>By default the send level is 0, so even if an effect is attached to the player
-     * this method must be called for the effect to be applied.
-     * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
-     * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
-     * so an appropriate conversion from linear UI input x to level is:
-     * x == 0 -> level = 0
-     * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
-     * @param level send level scalar
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     */
-    // This is an asynchronous call.
-    public @NonNull Object setAuxEffectSendLevel(float level) {
-        return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) {
-            @Override
-            void process() {
-                native_setAuxEffectSendLevel(level);
-            }
-        });
-    }
-
-    private native void native_setAuxEffectSendLevel(float level);
-
-    private static native void native_stream_event_onTearDown(
-            long nativeCallbackPtr, long userDataPtr);
-    private static native void native_stream_event_onStreamPresentationEnd(
-            long nativeCallbackPtr, long userDataPtr);
-    private static native void native_stream_event_onStreamDataRequest(
-            long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr);
-
-    /* Do not change these values (starting with INVOKE_ID) without updating
-     * their counterparts in include/media/mediaplayer2.h!
-     */
-    private static final int INVOKE_ID_GET_TRACK_INFO = 1;
-    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
-    private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
-    private static final int INVOKE_ID_SELECT_TRACK = 4;
-    private static final int INVOKE_ID_DESELECT_TRACK = 5;
-    private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
-
-    /**
-     * Invoke a generic method on the native player using opaque protocol
-     * buffer message for the request and reply. Both payloads' format is a
-     * convention between the java caller and the native player.
-     *
-     * @param msg PlayerMessage for the extension.
-     *
-     * @return PlayerMessage with the data returned by the
-     * native player.
-     */
-    private PlayerMessage invoke(PlayerMessage msg) {
-        byte[] ret = native_invoke(msg.toByteArray());
-        if (ret == null) {
-            return null;
-        }
-        try {
-            return PlayerMessage.parseFrom(ret);
-        } catch (InvalidProtocolBufferException e) {
-            return null;
-        }
-    }
-
-    private native byte[] native_invoke(byte[] request);
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "MEDIA_TRACK_TYPE", value = {
-            TrackInfo.MEDIA_TRACK_TYPE_VIDEO,
-            TrackInfo.MEDIA_TRACK_TYPE_AUDIO,
-            TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface TrackType {}
-
-    /**
-     * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
-     *
-     * @see MediaPlayer2#getTrackInfo
-     */
-    public static class TrackInfo {
-        /**
-         * Gets the track type.
-         * @return TrackType which indicates if the track is video, audio, timed text.
-         */
-        public int getTrackType() {
-            return mTrackType;
-        }
-
-        /**
-         * Gets the language code of the track.
-         * @return a language code in either way of ISO-639-1 or ISO-639-2.
-         * When the language is unknown or could not be determined,
-         * ISO-639-2 language code, "und", is returned.
-         */
-        public @NonNull String getLanguage() {
-            String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
-            return language == null ? "und" : language;
-        }
-
-        /**
-         * Gets the {@link MediaFormat} of the track.  If the format is
-         * unknown or could not be determined, null is returned.
-         */
-        public @Nullable MediaFormat getFormat() {
-            if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
-                    || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-                return mFormat;
-            }
-            return null;
-        }
-
-        public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
-        public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
-        public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
-
-        /** @hide */
-        public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
-
-        public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
-        public static final int MEDIA_TRACK_TYPE_METADATA = 5;
-
-        final int mId;
-        final int mTrackType;
-        final MediaFormat mFormat;
-
-        static TrackInfo create(int idx, Iterator<Value> in) {
-            int trackType = in.next().getInt32Value();
-            // TODO: build the full MediaFormat; currently we are using createSubtitleFormat
-            // even for audio/video tracks, meaning we only set the mime and language.
-            String mime = in.next().getStringValue();
-            String language = in.next().getStringValue();
-            MediaFormat format = MediaFormat.createSubtitleFormat(mime, language);
-
-            if (trackType == MEDIA_TRACK_TYPE_SUBTITLE) {
-                format.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
-                format.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
-                format.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
-            }
-            return new TrackInfo(idx, trackType, format);
-        }
-
-        /** @hide */
-        TrackInfo(int id, int type, MediaFormat format) {
-            mId = id;
-            mTrackType = type;
-            mFormat = format;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder out = new StringBuilder(128);
-            out.append(getClass().getName());
-            out.append('{');
-            switch (mTrackType) {
-                case MEDIA_TRACK_TYPE_VIDEO:
-                    out.append("VIDEO");
-                    break;
-                case MEDIA_TRACK_TYPE_AUDIO:
-                    out.append("AUDIO");
-                    break;
-                case MEDIA_TRACK_TYPE_TIMEDTEXT:
-                    out.append("TIMEDTEXT");
-                    break;
-                case MEDIA_TRACK_TYPE_SUBTITLE:
-                    out.append("SUBTITLE");
-                    break;
-                default:
-                    out.append("UNKNOWN");
-                    break;
-            }
-            out.append(", " + mFormat.toString());
-            out.append("}");
-            return out.toString();
-        }
-    };
-
-    /**
-     * Returns a List of track information of current data source.
-     * Same as {@link #getTrackInfo(DataSourceDesc)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @return List of track info. The total number of tracks is the array length.
-     * Must be called again if an external timed text source has been added after
-     * addTimedTextSource method is called.
-     * @throws IllegalStateException if it is called in an invalid state.
-     * @throws NullPointerException if current data source is null
-     */
-    public @NonNull List<TrackInfo> getTrackInfo() {
-        return getTrackInfo(getCurrentDataSource());
-    }
-
-    /**
-     * Returns a List of track information.
-     *
-     * @param dsd the descriptor of data source of which you want to get track info
-     * @return List of track info. The total number of tracks is the array length.
-     * Must be called again if an external timed text source has been added after
-     * addTimedTextSource method is called.
-     * @throws IllegalStateException if it is called in an invalid state.
-     * @throws NullPointerException if dsd is null
-     */
-    public @NonNull List<TrackInfo> getTrackInfo(@NonNull DataSourceDesc dsd) {
-        if (dsd == null) {
-            throw new NullPointerException("non-null dsd is expected");
-        }
-        SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo == null) {
-            return new ArrayList<TrackInfo>(0);
-        }
-
-        TrackInfo[] trackInfo = getInbandTrackInfo(sourceInfo);
-        return (trackInfo != null ? Arrays.asList(trackInfo) : new ArrayList<TrackInfo>(0));
-    }
-
-    private TrackInfo[] getInbandTrackInfo(SourceInfo sourceInfo) throws IllegalStateException {
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
-                .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
-                .build();
-        PlayerMessage response = invoke(request);
-        if (response == null) {
-            return null;
-        }
-        Iterator<Value> in = response.getValuesList().iterator();
-        int size = in.next().getInt32Value();
-        if (size == 0) {
-            return null;
-        }
-        TrackInfo[] trackInfo = new TrackInfo[size];
-        for (int i = 0; i < size; ++i) {
-            trackInfo[i] = TrackInfo.create(i, in);
-        }
-        return trackInfo;
-    }
-
-    /**
-     * Returns the index of the audio, video, or subtitle track currently selected for playback.
-     * The return value is an index into the array returned by {@link #getTrackInfo}, and can
-     * be used in calls to {@link #selectTrack(TrackInfo)} or {@link #deselectTrack(TrackInfo)}.
-     * Same as {@link #getSelectedTrack(DataSourceDesc, int)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
-     * @return metadata corresponding to the audio, video, or subtitle track currently selected for
-     * playback; {@code null} is returned when there is no selected track for {@code trackType} or
-     * when {@code trackType} is not one of audio, video, or subtitle.
-     * @throws IllegalStateException if called after {@link #close()}
-     * @throws NullPointerException if current data source is null
-     *
-     * @see #getTrackInfo()
-     * @see #selectTrack(TrackInfo)
-     * @see #deselectTrack(TrackInfo)
-     */
-    @Nullable
-    public TrackInfo getSelectedTrack(@TrackType int trackType) {
-        return getSelectedTrack(getCurrentDataSource(), trackType);
-    }
-
-    /**
-     * Returns the index of the audio, video, or subtitle track currently selected for playback.
-     * The return value is an index into the array returned by {@link #getTrackInfo}, and can
-     * be used in calls to {@link #selectTrack(DataSourceDesc, TrackInfo)} or
-     * {@link #deselectTrack(DataSourceDesc, TrackInfo)}.
-     *
-     * @param dsd the descriptor of data source of which you want to get selected track
-     * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
-     * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
-     * @return metadata corresponding to the audio, video, or subtitle track currently selected for
-     * playback; {@code null} is returned when there is no selected track for {@code trackType} or
-     * when {@code trackType} is not one of audio, video, or subtitle.
-     * @throws IllegalStateException if called after {@link #close()}
-     * @throws NullPointerException if dsd is null
-     *
-     * @see #getTrackInfo(DataSourceDesc)
-     * @see #selectTrack(DataSourceDesc, TrackInfo)
-     * @see #deselectTrack(DataSourceDesc, TrackInfo)
-     */
-    @Nullable
-    public TrackInfo getSelectedTrack(@NonNull DataSourceDesc dsd, @TrackType int trackType) {
-        if (dsd == null) {
-            throw new NullPointerException("non-null dsd is expected");
-        }
-        SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo == null) {
-            return null;
-        }
-
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
-                .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
-                .addValues(Value.newBuilder().setInt32Value(trackType))
-                .build();
-        PlayerMessage response = invoke(request);
-        if (response == null) {
-            return null;
-        }
-        // TODO: return full TrackInfo data from native player instead of index
-        final int idx = response.getValues(0).getInt32Value();
-        final List<TrackInfo> trackInfos = getTrackInfo(dsd);
-        return trackInfos.isEmpty() ? null : trackInfos.get(idx);
-    }
-
-    /**
-     * Selects a track of current data source.
-     * Same as {@link #selectTrack(DataSourceDesc, TrackInfo)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
-     * object can be obtained from {@link #getTrackInfo()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     *
-     * This is an asynchronous call.
-     *
-     * @see MediaPlayer2#getTrackInfo()
-     */
-    @NonNull
-    public Object selectTrack(@NonNull TrackInfo trackInfo) {
-        return selectTrack(getCurrentDataSource(), trackInfo);
-    }
-
-    /**
-     * Selects a track.
-     * <p>
-     * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
-     * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
-     * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
-     * </p>
-     * <p>
-     * In any valid state, if it is called multiple times on the same type of track (ie. Video,
-     * Audio, Timed Text), the most recent one will be chosen.
-     * </p>
-     * <p>
-     * The first audio and video tracks are selected by default if available, even though
-     * this method is not called. However, no timed text track will be selected until
-     * this function is called.
-     * </p>
-     * <p>
-     * Currently, only timed text tracks or audio tracks can be selected via this method.
-     * In addition, the support for selecting an audio track at runtime is pretty limited
-     * in that an audio track can only be selected in the <em>Prepared</em> state.
-     * </p>
-     * @param dsd the descriptor of data source of which you want to select track
-     * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
-     * object can be obtained from {@link #getTrackInfo()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     *
-     * This is an asynchronous call.
-     *
-     * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
-     */
-    @NonNull
-    public Object selectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) {
-        return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) {
-            @Override
-            void process() {
-                selectOrDeselectTrack(dsd, trackInfo.mId, true /* select */);
-            }
-        });
-    }
-
-    /**
-     * Deselect a track of current data source.
-     * Same as {@link #deselectTrack(DataSourceDesc, TrackInfo)} with
-     * {@code dsd = getCurrentDataSource()}.
-     *
-     * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
-     * object can be obtained from {@link #getTrackInfo()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     *
-     * This is an asynchronous call.
-     *
-     * @see MediaPlayer2#getTrackInfo()
-     */
-    @NonNull
-    public Object deselectTrack(@NonNull TrackInfo trackInfo) {
-        return deselectTrack(getCurrentDataSource(), trackInfo);
-    }
-
-    /**
-     * Deselect a track.
-     * <p>
-     * Currently, the track must be a timed text track and no audio or video tracks can be
-     * deselected. If the timed text track identified by index has not been
-     * selected before, it throws an exception.
-     * </p>
-     * @param dsd the descriptor of data source of which you want to deselect track
-     * @param trackInfo metadata corresponding to the track to be selected. A {@code trackInfo}
-     * object can be obtained from {@link #getTrackInfo()}.
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     *
-     * This is an asynchronous call.
-     *
-     * @see MediaPlayer2#getTrackInfo(DataSourceDesc)
-     */
-    @NonNull
-    public Object deselectTrack(@NonNull DataSourceDesc dsd, @NonNull TrackInfo trackInfo) {
-        return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) {
-            @Override
-            void process() {
-                selectOrDeselectTrack(dsd, trackInfo.mId, false /* select */);
-            }
-        });
-    }
-
-    private void selectOrDeselectTrack(@NonNull DataSourceDesc dsd, int index, boolean select) {
-        if (dsd == null) {
-            throw new IllegalArgumentException("non-null dsd is expected");
-        }
-        SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo == null) {
-            return;
-        }
-
-        PlayerMessage request = PlayerMessage.newBuilder()
-                .addValues(Value.newBuilder().setInt32Value(
-                            select ? INVOKE_ID_SELECT_TRACK : INVOKE_ID_DESELECT_TRACK))
-                .addValues(Value.newBuilder().setInt64Value(sourceInfo.mId))
-                .addValues(Value.newBuilder().setInt32Value(index))
-                .build();
-        invoke(request);
-    }
-
-    /* Do not change these values without updating their counterparts
-     * in include/media/mediaplayer2.h!
-     */
-    private static final int MEDIA_NOP = 0; // interface test message
-    private static final int MEDIA_PREPARED = 1;
-    private static final int MEDIA_PLAYBACK_COMPLETE = 2;
-    private static final int MEDIA_BUFFERING_UPDATE = 3;
-    private static final int MEDIA_SEEK_COMPLETE = 4;
-    private static final int MEDIA_SET_VIDEO_SIZE = 5;
-    private static final int MEDIA_STARTED = 6;
-    private static final int MEDIA_PAUSED = 7;
-    private static final int MEDIA_STOPPED = 8;
-    private static final int MEDIA_SKIPPED = 9;
-    private static final int MEDIA_DRM_PREPARED = 10;
-    private static final int MEDIA_NOTIFY_TIME = 98;
-    private static final int MEDIA_TIMED_TEXT = 99;
-    private static final int MEDIA_ERROR = 100;
-    private static final int MEDIA_INFO = 200;
-    private static final int MEDIA_SUBTITLE_DATA = 201;
-    private static final int MEDIA_META_DATA = 202;
-    private static final int MEDIA_DRM_INFO = 210;
-
-    private class TaskHandler extends Handler {
-        private MediaPlayer2 mMediaPlayer;
-
-        TaskHandler(MediaPlayer2 mp, Looper looper) {
-            super(looper);
-            mMediaPlayer = mp;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            handleMessage(msg, 0);
-        }
-
-        public void handleMessage(Message msg, long srcId) {
-            if (mMediaPlayer.mNativeContext == 0) {
-                Log.w(TAG, "mediaplayer2 went away with unhandled events");
-                return;
-            }
-            final int what = msg.arg1;
-            final int extra = msg.arg2;
-
-            final SourceInfo sourceInfo = getSourceInfo(srcId);
-            if (sourceInfo == null) {
-                return;
-            }
-            final DataSourceDesc dsd = sourceInfo.mDSD;
-
-            switch(msg.what) {
-                case MEDIA_PREPARED:
-                case MEDIA_DRM_PREPARED:
-                {
-                    sourceInfo.mPrepareBarrier--;
-                    if (sourceInfo.mPrepareBarrier > 0) {
-                        break;
-                    } else if (sourceInfo.mPrepareBarrier < 0) {
-                        Log.w(TAG, "duplicated (drm) prepared events");
-                        break;
-                    }
-
-                    if (dsd != null) {
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onInfo(
-                                        mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0);
-                            }
-                        });
-                    }
-
-                    synchronized (mSrcLock) {
-                        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                        Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId
-                                + ", curSrc=" + mCurrentSourceInfo
-                                + ", nextSrc=" + nextSourceInfo);
-
-                        if (isCurrentSource(srcId)) {
-                            prepareNextDataSource();
-                        } else if (isNextSource(srcId)) {
-                            nextSourceInfo.mStateAsNextSource = NEXT_SOURCE_STATE_PREPARED;
-                            if (nextSourceInfo.mPlayPendingAsNextSource) {
-                                playNextDataSource();
-                            }
-                        }
-                    }
-
-                    synchronized (mTaskLock) {
-                        if (mCurrentTask != null
-                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE
-                                && mCurrentTask.mDSD == dsd
-                                && mCurrentTask.mNeedToWaitForEventToComplete) {
-                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                            mCurrentTask = null;
-                            processPendingTask_l();
-                        }
-                    }
-                    return;
-                }
-
-                case MEDIA_DRM_INFO:
-                {
-                    if (msg.obj == null) {
-                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
-                    } else if (msg.obj instanceof byte[]) {
-                        // The PlayerMessage was parsed already in postEventFromNative
-
-                        final DrmInfo drmInfo;
-                        synchronized (sourceInfo) {
-                            if (sourceInfo.mDrmInfo != null) {
-                                drmInfo = sourceInfo.mDrmInfo.makeCopy();
-                            } else {
-                                drmInfo = null;
-                            }
-                        }
-
-                        // notifying the client outside the lock
-                        DrmPreparationInfo drmPrepareInfo = null;
-                        if (drmInfo != null) {
-                            try {
-                                drmPrepareInfo = sendDrmEventWait(
-                                        new DrmEventNotifier<DrmPreparationInfo>() {
-                                            @Override
-                                            public DrmPreparationInfo notifyWait(
-                                                    DrmEventCallback callback) {
-                                                return callback.onDrmInfo(mMediaPlayer, dsd,
-                                                        drmInfo);
-                                            }
-                                        });
-                            } catch (InterruptedException | ExecutionException
-                                    | TimeoutException e) {
-                                Log.w(TAG, "Exception while waiting for DrmPreparationInfo", e);
-                            }
-                        }
-                        if (sourceInfo.mDrmHandle.setPreparationInfo(drmPrepareInfo)) {
-                            sourceInfo.mPrepareBarrier++;
-                            final Task prepareDrmTask;
-                            prepareDrmTask = newPrepareDrmTask(dsd, drmPrepareInfo.mUUID);
-                            mTaskHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    // Run as simple Runnable, not Task
-                                    try {
-                                        prepareDrmTask.process();
-                                    } catch (NoDrmSchemeException | IOException e) {
-                                        final String errMsg;
-                                        errMsg = "Unexpected Exception during prepareDrm";
-                                        throw new RuntimeException(errMsg, e);
-                                    }
-                                }
-                            });
-                        } else {
-                            Log.w(TAG, "No valid DrmPreparationInfo set");
-                        }
-                    } else {
-                        Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
-                    }
-                    return;
-                }
-
-                case MEDIA_PLAYBACK_COMPLETE:
-                {
-                    if (isCurrentSource(srcId)) {
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onInfo(
-                                        mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
-                            }
-                        });
-                        stayAwake(false);
-
-                        synchronized (mSrcLock) {
-                            SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-                            if (nextSourceInfo != null) {
-                                nextSourceInfo.mPlayPendingAsNextSource = true;
-                            }
-                            Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId
-                                    + ", curSrc=" + mCurrentSourceInfo
-                                    + ", nextSrc=" + nextSourceInfo);
-                        }
-
-                        playNextDataSource();
-                    }
-
-                    return;
-                }
-
-                case MEDIA_STOPPED:
-                case MEDIA_STARTED:
-                case MEDIA_PAUSED:
-                case MEDIA_SKIPPED:
-                case MEDIA_NOTIFY_TIME:
-                {
-                    // Do nothing. The client should have enough information with
-                    // {@link EventCallback#onMediaTimeDiscontinuity}.
-                    break;
-                }
-
-                case MEDIA_BUFFERING_UPDATE:
-                {
-                    final int percent = msg.arg1;
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, percent);
-                        }
-                    });
-
-                    SourceInfo src = getSourceInfo(srcId);
-                    if (src != null) {
-                        src.mBufferedPercentage.set(percent);
-                    }
-
-                    return;
-                }
-
-                case MEDIA_SEEK_COMPLETE:
-                {
-                    synchronized (mTaskLock) {
-                        if (!mPendingTasks.isEmpty()
-                                && mPendingTasks.get(0).mMediaCallType != CALL_COMPLETED_SEEK_TO
-                                && getState() == PLAYER_STATE_PLAYING) {
-                            mIsPreviousCommandSeekTo = false;
-                        }
-
-                        if (mCurrentTask != null
-                                && mCurrentTask.mMediaCallType == CALL_COMPLETED_SEEK_TO
-                                && mCurrentTask.mNeedToWaitForEventToComplete) {
-                            mCurrentTask.sendCompleteNotification(CALL_STATUS_NO_ERROR);
-                            mCurrentTask = null;
-                            processPendingTask_l();
-                        }
-                    }
-                    return;
-                }
-
-                case MEDIA_SET_VIDEO_SIZE:
-                {
-                    final int width = msg.arg1;
-                    final int height = msg.arg2;
-
-                    mVideoSize = new Size(width, height);
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onVideoSizeChanged(
-                                    mMediaPlayer, dsd, mVideoSize);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_ERROR:
-                {
-                    Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onError(
-                                    mMediaPlayer, dsd, what, extra);
-                        }
-                    });
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, MEDIA_INFO_DATA_SOURCE_END, 0);
-                        }
-                    });
-                    stayAwake(false);
-                    return;
-                }
-
-                case MEDIA_INFO:
-                {
-                    switch (msg.arg1) {
-                        case MEDIA_INFO_VIDEO_TRACK_LAGGING:
-                            Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
-                            break;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onInfo(
-                                    mMediaPlayer, dsd, what, extra);
-                        }
-                    });
-
-                    if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
-                        if (isCurrentSource(srcId)) {
-                            prepareNextDataSource();
-                        }
-                    }
-
-                    // No real default action so far.
-                    return;
-                }
-
-                case MEDIA_TIMED_TEXT:
-                {
-                    final TimedText text;
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse timed text.", e);
-                            return;
-                        }
-                        text = TimedTextUtil.parsePlayerMessage(playerMsg);
-                    } else {
-                        text = null;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onTimedText(
-                                    mMediaPlayer, dsd, text);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_SUBTITLE_DATA:
-                {
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse subtitle data.", e);
-                            return;
-                        }
-                        Iterator<Value> in = playerMsg.getValuesList().iterator();
-                        final int trackIndex = in.next().getInt32Value();
-                        TrackInfo trackInfo = getTrackInfo(dsd).get(trackIndex);
-                        final long startTimeUs = in.next().getInt64Value();
-                        final long durationTimeUs = in.next().getInt64Value();
-                        final byte[] subData = in.next().getBytesValue().toByteArray();
-                        SubtitleData data = new SubtitleData(trackInfo,
-                                startTimeUs, durationTimeUs, subData);
-                        sendEvent(new EventNotifier() {
-                            @Override
-                            public void notify(EventCallback callback) {
-                                callback.onSubtitleData(
-                                        mMediaPlayer, dsd, data);
-                            }
-                        });
-                    }
-                    return;
-                }
-
-                case MEDIA_META_DATA:
-                {
-                    final TimedMetaData data;
-                    if (msg.obj instanceof byte[]) {
-                        PlayerMessage playerMsg;
-                        try {
-                            playerMsg = PlayerMessage.parseFrom((byte[]) msg.obj);
-                        } catch (InvalidProtocolBufferException e) {
-                            Log.w(TAG, "Failed to parse timed meta data.", e);
-                            return;
-                        }
-                        Iterator<Value> in = playerMsg.getValuesList().iterator();
-                        data = new TimedMetaData(
-                                in.next().getInt64Value(),  // timestampUs
-                                in.next().getBytesValue().toByteArray());  // metaData
-                    } else {
-                        data = null;
-                    }
-
-                    sendEvent(new EventNotifier() {
-                        @Override
-                        public void notify(EventCallback callback) {
-                            callback.onTimedMetaDataAvailable(
-                                    mMediaPlayer, dsd, data);
-                        }
-                    });
-                    return;
-                }
-
-                case MEDIA_NOP: // interface test message - ignore
-                {
-                    break;
-                }
-
-                default:
-                {
-                    Log.e(TAG, "Unknown message type " + msg.what);
-                    return;
-                }
-            }
-        }
-    }
-
-    /*
-     * Called from native code when an interesting event happens.  This method
-     * just uses the TaskHandler system to post the event back to the main app thread.
-     * We use a weak reference to the original MediaPlayer2 object so that the native
-     * code is safe from the object disappearing from underneath it.  (This is
-     * the cookie passed to native_setup().)
-     */
-    private static void postEventFromNative(Object mediaplayer2Ref, long srcId,
-                                            int what, int arg1, int arg2, byte[] obj) {
-        final MediaPlayer2 mp = (MediaPlayer2) ((WeakReference) mediaplayer2Ref).get();
-        if (mp == null) {
-            return;
-        }
-
-        final SourceInfo sourceInfo = mp.getSourceInfo(srcId);
-        switch (what) {
-            case MEDIA_DRM_INFO:
-                // We need to derive mDrmInfo before prepare() returns so processing it here
-                // before the notification is sent to TaskHandler below. TaskHandler runs in the
-                // notification looper so its handleMessage might process the event after prepare()
-                // has returned.
-                Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
-                if (obj != null && sourceInfo != null) {
-                    PlayerMessage playerMsg;
-                    try {
-                        playerMsg = PlayerMessage.parseFrom(obj);
-                    } catch (InvalidProtocolBufferException e) {
-                        Log.w(TAG, "MEDIA_DRM_INFO failed to parse msg.obj " + obj);
-                        break;
-                    }
-                    DrmInfo drmInfo = DrmInfo.create(playerMsg);
-                    synchronized (sourceInfo) {
-                        sourceInfo.mDrmInfo = drmInfo;
-                    }
-                } else {
-                    Log.w(TAG, "MEDIA_DRM_INFO sourceInfo " + sourceInfo
-                            + " msg.obj of unexpected type " + obj);
-                }
-                break;
-
-            case MEDIA_PREPARED:
-                // By this time, we've learned about DrmInfo's presence or absence. This is meant
-                // mainly for prepare() use case. For prepare(), this still can run to a race
-                // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
-                // so we also set mDrmInfoResolved in prepare().
-                if (sourceInfo != null) {
-                    synchronized (sourceInfo) {
-                        sourceInfo.mDrmInfoResolved = true;
-                    }
-                }
-                break;
-        }
-
-        if (mp.mTaskHandler != null) {
-            Message m = mp.mTaskHandler.obtainMessage(what, arg1, arg2, obj);
-
-            mp.mTaskHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mp.mTaskHandler.handleMessage(m, srcId);
-                }
-            });
-        }
-    }
-
-    /**
-     * Class encapsulating subtitle data, as received through the
-     * {@link EventCallback#onSubtitleData} interface.
-     * <p>
-     * A {@link SubtitleData} object includes:
-     * <ul>
-     * <li> track metadadta in a {@link TrackInfo} object</li>
-     * <li> the start time (in microseconds) of the data</li>
-     * <li> the duration (in microseconds) of the data</li>
-     * <li> the actual data.</li>
-     * </ul>
-     * The data is stored in a byte-array, and is encoded in one of the supported in-band
-     * subtitle formats. The subtitle encoding is determined by the MIME type of the
-     * {@link TrackInfo} of the subtitle track, one of
-     * {@link MediaFormat#MIMETYPE_TEXT_CEA_608}, {@link MediaFormat#MIMETYPE_TEXT_CEA_708},
-     * {@link MediaFormat#MIMETYPE_TEXT_VTT}.
-     */
-    public static final class SubtitleData {
-
-        private TrackInfo mTrackInfo;
-        private long mStartTimeUs;
-        private long mDurationUs;
-        private byte[] mData;
-
-        private SubtitleData(TrackInfo trackInfo, long startTimeUs, long durationUs, byte[] data) {
-            mTrackInfo = trackInfo;
-            mStartTimeUs = startTimeUs;
-            mDurationUs = durationUs;
-            mData = (data != null ? data : new byte[0]);
-        }
-
-        /**
-         * @return metadata of track which contains this subtitle data
-         */
-        @NonNull
-        public TrackInfo getTrackInfo() {
-            return mTrackInfo;
-        }
-
-        /**
-         * @return media time at which the subtitle should start to be displayed in microseconds
-         */
-        public long getStartTimeUs() {
-            return mStartTimeUs;
-        }
-
-        /**
-         * @return the duration in microsecond during which the subtitle should be displayed
-         */
-        public long getDurationUs() {
-            return mDurationUs;
-        }
-
-        /**
-         * Returns the encoded data for the subtitle content.
-         * Encoding format depends on the subtitle type, refer to
-         * <a href="https://en.wikipedia.org/wiki/CEA-708">CEA 708</a>,
-         * <a href="https://en.wikipedia.org/wiki/EIA-608">CEA/EIA 608</a> and
-         * <a href="https://www.w3.org/TR/webvtt1/">WebVTT</a>, defined by the MIME type
-         * of the subtitle track.
-         * @return the encoded subtitle data
-         */
-        @NonNull
-        public byte[] getData() {
-            return mData;
-        }
-    }
-
-    /**
-     * Interface definition for callbacks to be invoked when the player has the corresponding
-     * events.
-     */
-    public static class EventCallback {
-        /**
-         * Called to indicate the video size
-         *
-         * The video size (width and height) could be 0 if there was no video,
-         * or the value was not determined yet.
-         *
-         * @param mp the MediaPlayer2 associated with this callback
-         * @param dsd the DataSourceDesc of this data source
-         * @param size the size of the video
-         */
-        public void onVideoSizeChanged(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull Size size) { }
-
-        /**
-         * Called to indicate an avaliable timed text
-         *
-         * @param mp the MediaPlayer2 associated with this callback
-         * @param dsd the DataSourceDesc of this data source
-         * @param text the timed text sample which contains the text
-         *             needed to be displayed and the display format.
-         * @hide
-         */
-        public void onTimedText(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @NonNull TimedText text) { }
-
-        /**
-         * Called to indicate avaliable timed metadata
-         * <p>
-         * This method will be called as timed metadata is extracted from the media,
-         * in the same order as it occurs in the media. The timing of this event is
-         * not controlled by the associated timestamp.
-         * <p>
-         * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates
-         * {@link TimedMetaData}.
-         *
-         * @see MediaPlayer2#selectTrack
-         * @see MediaPlayer2.OnTimedMetaDataAvailableListener
-         * @see TimedMetaData
-         *
-         * @param mp the MediaPlayer2 associated with this callback
-         * @param dsd the DataSourceDesc of this data source
-         * @param data the timed metadata sample associated with this event
-         */
-        public void onTimedMetaDataAvailable(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @NonNull TimedMetaData data) { }
-
-        /**
-         * Called to indicate an error.
-         *
-         * @param mp the MediaPlayer2 the error pertains to
-         * @param dsd the DataSourceDesc of this data source
-         * @param what the type of error that has occurred.
-         * @param extra an extra code, specific to the error. Typically
-         * implementation dependent.
-         */
-        public void onError(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @MediaError int what, int extra) { }
-
-        /**
-         * Called to indicate an info or a warning.
-         *
-         * @param mp the MediaPlayer2 the info pertains to.
-         * @param dsd the DataSourceDesc of this data source
-         * @param what the type of info or warning.
-         * @param extra an extra code, specific to the info. Typically
-         * implementation dependent.
-         */
-        public void onInfo(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @MediaInfo int what, int extra) { }
-
-        /**
-         * Called to acknowledge an API call.
-         *
-         * @param mp the MediaPlayer2 the call was made on.
-         * @param dsd the DataSourceDesc of this data source
-         * @param what the enum for the API call.
-         * @param status the returned status code for the call.
-         */
-        public void onCallCompleted(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd, @CallCompleted int what,
-                @CallStatus int status) { }
-
-        /**
-         * Called to indicate media clock has changed.
-         *
-         * @param mp the MediaPlayer2 the media time pertains to.
-         * @param dsd the DataSourceDesc of this data source
-         * @param timestamp the new media clock.
-         */
-        public void onMediaTimeDiscontinuity(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @NonNull MediaTimestamp timestamp) { }
-
-        /**
-         * Called to indicate {@link #notifyWhenCommandLabelReached(Object)} has been processed.
-         *
-         * @param mp the MediaPlayer2 {@link #notifyWhenCommandLabelReached(Object)} was called on.
-         * @param label the application specific Object given by
-         *        {@link #notifyWhenCommandLabelReached(Object)}.
-         */
-        public void onCommandLabelReached(@NonNull MediaPlayer2 mp, @NonNull Object label) { }
-
-        /**
-         * Called when when a player subtitle track has new subtitle data available.
-         * @param mp the player that reports the new subtitle data
-         * @param dsd the DataSourceDesc of this data source
-         * @param data the subtitle data
-         */
-        public void onSubtitleData(
-                @NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @NonNull SubtitleData data) { }
-    }
-
-    private final Object mEventCbLock = new Object();
-    private ArrayList<Pair<Executor, EventCallback>> mEventCallbackRecords =
-            new ArrayList<Pair<Executor, EventCallback>>();
-
-    /**
-     * Registers the callback to be invoked for various events covered by {@link EventCallback}.
-     *
-     * @param executor the executor through which the callback should be invoked
-     * @param eventCallback the callback that will be run
-     */
-    // This is a synchronous call.
-    public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull EventCallback eventCallback) {
-        if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null EventCallback");
-        }
-        if (executor == null) {
-            throw new IllegalArgumentException(
-                    "Illegal null Executor for the EventCallback");
-        }
-        synchronized (mEventCbLock) {
-            for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
-                if (cb.first == executor && cb.second == eventCallback) {
-                    Log.w(TAG, "The callback has been registered before.");
-                    return;
-                }
-            }
-            mEventCallbackRecords.add(new Pair(executor, eventCallback));
-        }
-    }
-
-    /**
-     * Unregisters the {@link EventCallback}.
-     *
-     * @param eventCallback the callback to be unregistered
-     */
-    // This is a synchronous call.
-    public void unregisterEventCallback(@NonNull EventCallback eventCallback) {
-        synchronized (mEventCbLock) {
-            for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
-                if (cb.second == eventCallback) {
-                    mEventCallbackRecords.remove(cb);
-                }
-            }
-        }
-    }
-
-    private void sendEvent(final EventNotifier notifier) {
-        synchronized (mEventCbLock) {
-            try {
-                for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
-                    cb.first.execute(() -> notifier.notify(cb.second));
-                }
-            } catch (RejectedExecutionException e) {
-                // The executor has been shut down.
-                Log.w(TAG, "The executor has been shut down. Ignoring event.");
-            }
-        }
-    }
-
-    private void sendDrmEvent(final DrmEventNotifier notifier) {
-        synchronized (mDrmEventCallbackLock) {
-            try {
-                Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
-                if (cb != null) {
-                    cb.first.execute(() -> notifier.notify(cb.second));
-                }
-            } catch (RejectedExecutionException e) {
-                // The executor has been shut down.
-                Log.w(TAG, "The executor has been shut down. Ignoring drm event.");
-            }
-        }
-    }
-
-    private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        return sendDrmEventWait(notifier, 0);
-    }
-
-    private <T> T sendDrmEventWait(final DrmEventNotifier<T> notifier, final long timeoutMs)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        synchronized (mDrmEventCallbackLock) {
-            Pair<Executor, DrmEventCallback> cb = mDrmEventCallback;
-            if (cb != null) {
-                CompletableFuture<T> ret = new CompletableFuture<>();
-                cb.first.execute(() -> ret.complete(notifier.notifyWait(cb.second)));
-                return timeoutMs <= 0 ? ret.get() : ret.get(timeoutMs, TimeUnit.MILLISECONDS);
-            }
-        }
-        return null;
-    }
-
-    private interface EventNotifier {
-        void notify(EventCallback callback);
-    }
-
-    private interface DrmEventNotifier<T> {
-        default void notify(DrmEventCallback callback) { }
-        default T notifyWait(DrmEventCallback callback) {
-            return null;
-        }
-    }
-
-    /* Do not change these values without updating their counterparts
-     * in include/media/MediaPlayer2Types.h!
-     */
-    /** Unspecified media player error.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_UNKNOWN = 1;
-
-    /**
-     * The video is streamed and its container is not valid for progressive
-     * playback i.e the video's index (e.g moov atom) is not at the start of the
-     * file.
-     * @see EventCallback#onError
-     */
-    public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
-
-    /** File or network related operation errors. */
-    public static final int MEDIA_ERROR_IO = -1004;
-    /** Bitstream is not conforming to the related coding standard or file spec. */
-    public static final int MEDIA_ERROR_MALFORMED = -1007;
-    /** Bitstream is conforming to the related coding standard or file spec, but
-     * the media framework does not support the feature. */
-    public static final int MEDIA_ERROR_UNSUPPORTED = -1010;
-    /** Some operation takes too long to complete, usually more than 3-5 seconds. */
-    public static final int MEDIA_ERROR_TIMED_OUT = -110;
-
-    /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
-     * system/core/include/utils/Errors.h
-     * @see EventCallback#onError
-     * @hide
-     */
-    public static final int MEDIA_ERROR_SYSTEM = -2147483648;
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "MEDIA_ERROR", value = {
-            MEDIA_ERROR_UNKNOWN,
-            MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK,
-            MEDIA_ERROR_IO,
-            MEDIA_ERROR_MALFORMED,
-            MEDIA_ERROR_UNSUPPORTED,
-            MEDIA_ERROR_TIMED_OUT,
-            MEDIA_ERROR_SYSTEM
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaError {}
-
-    /* Do not change these values without updating their counterparts
-     * in include/media/MediaPlayer2Types.h!
-     */
-    /** Unspecified media player info.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_UNKNOWN = 1;
-
-    /** The player just started the playback of this datas source.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_START = 2;
-
-    /** The player just pushed the very first video frame for rendering.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
-
-    /** The player just rendered the very first audio sample.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
-
-    /** The player just completed the playback of this data source.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_END = 5;
-
-    /** The player just completed the playback of all data sources set by {@link #setDataSource},
-     * {@link #setNextDataSource} and {@link #setNextDataSources}.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6;
-
-    /** The player just completed an iteration of playback loop. This event is sent only when
-     *  looping is enabled by {@link #loopCurrent}.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7;
-
-    /** The player just prepared a data source.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_PREPARED = 100;
-
-    /** The video is too complex for the decoder: it can't decode frames fast
-     *  enough. Possibly only the audio plays fine at this stage.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
-
-    /** MediaPlayer2 is temporarily pausing playback internally in order to
-     * buffer more data.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_START = 701;
-
-    /** MediaPlayer2 is resuming playback after filling buffers.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_END = 702;
-
-    /** Estimated network bandwidth information (kbps) is available; currently this event fires
-     * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
-     * when playing network files.
-     * @see EventCallback#onInfo
-     * @hide
-     */
-    public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
-
-    /**
-     * Update status in buffering a media source received through progressive downloading.
-     * The received buffering percentage indicates how much of the content has been buffered
-     * or played. For example a buffering update of 80 percent when half the content
-     * has already been played indicates that the next 30 percent of the
-     * content to play has been buffered.
-     *
-     * The {@code extra} parameter in {@code EventCallback.onInfo} is the
-     * percentage (0-100) of the content that has been buffered or played thus far.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
-
-    /** Bad interleaving means that a media has been improperly interleaved or
-     * not interleaved at all, e.g has all the video samples first then all the
-     * audio ones. Video is playing but a lot of disk seeks may be happening.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
-
-    /** The media cannot be seeked (e.g live stream)
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
-
-    /** A new set of metadata is available.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_METADATA_UPDATE = 802;
-
-    /** Informs that audio is not playing. Note that playback of the video
-     * is not interrupted.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
-
-    /** Informs that video is not playing. Note that playback of the audio
-     * is not interrupted.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
-
-    /** Failed to handle timed text track properly.
-     * @see EventCallback#onInfo
-     *
-     * {@hide}
-     */
-    public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
-
-    /** Subtitle track was not supported by the media framework.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
-
-    /** Reading the subtitle track takes too long.
-     * @see EventCallback#onInfo
-     */
-    public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "MEDIA_INFO", value = {
-            MEDIA_INFO_UNKNOWN,
-            MEDIA_INFO_DATA_SOURCE_START,
-            MEDIA_INFO_VIDEO_RENDERING_START,
-            MEDIA_INFO_AUDIO_RENDERING_START,
-            MEDIA_INFO_DATA_SOURCE_END,
-            MEDIA_INFO_DATA_SOURCE_LIST_END,
-            MEDIA_INFO_PREPARED,
-            MEDIA_INFO_VIDEO_TRACK_LAGGING,
-            MEDIA_INFO_BUFFERING_START,
-            MEDIA_INFO_BUFFERING_END,
-            MEDIA_INFO_NETWORK_BANDWIDTH,
-            MEDIA_INFO_BUFFERING_UPDATE,
-            MEDIA_INFO_BAD_INTERLEAVING,
-            MEDIA_INFO_NOT_SEEKABLE,
-            MEDIA_INFO_METADATA_UPDATE,
-            MEDIA_INFO_AUDIO_NOT_PLAYING,
-            MEDIA_INFO_VIDEO_NOT_PLAYING,
-            MEDIA_INFO_TIMED_TEXT_ERROR,
-            MEDIA_INFO_UNSUPPORTED_SUBTITLE,
-            MEDIA_INFO_SUBTITLE_TIMED_OUT
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaInfo {}
-
-    //--------------------------------------------------------------------------
-    /** The player just completed a call {@link #attachAuxEffect}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1;
-
-    /** The player just completed a call {@link #deselectTrack}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_DESELECT_TRACK = 2;
-
-    /** The player just completed a call {@link #loopCurrent}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_LOOP_CURRENT = 3;
-
-    /** The player just completed a call {@link #pause}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PAUSE = 4;
-
-    /** The player just completed a call {@link #play}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PLAY = 5;
-
-    /** The player just completed a call {@link #prepare}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_PREPARE = 6;
-
-    /** The player just completed a call {@link #seekTo}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SEEK_TO = 14;
-
-    /** The player just completed a call {@link #selectTrack}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SELECT_TRACK = 15;
-
-    /** The player just completed a call {@link #setAudioAttributes}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16;
-
-    /** The player just completed a call {@link #setAudioSessionId}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17;
-
-    /** The player just completed a call {@link #setAuxEffectSendLevel}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18;
-
-    /** The player just completed a call {@link #setDataSource}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19;
-
-    /** The player just completed a call {@link #setNextDataSource}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22;
-
-    /** The player just completed a call {@link #setNextDataSources}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23;
-
-    /** The player just completed a call {@link #setPlaybackParams}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24;
-
-    /** The player just completed a call {@link #setPlayerVolume}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26;
-
-    /** The player just completed a call {@link #setSurface}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_SURFACE = 27;
-
-    /** The player just completed a call {@link #setSyncParams}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_SYNC_PARAMS = 28;
-
-    /** The player just completed a call {@link #skipToNext}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
-
-    /** The player just completed a call {@link #clearNextDataSources}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES = 30;
-
-    /** The player just completed a call {@link #setBufferingParams}.
-     * @see EventCallback#onCallCompleted
-     * @hide
-     */
-    public static final int CALL_COMPLETED_SET_BUFFERING_PARAMS = 31;
-
-    /** The player just completed a call {@link #setDisplay}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_DISPLAY = 33;
-
-    /** The player just completed a call {@link #setWakeLock}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_WAKE_LOCK = 34;
-
-    /** The player just completed a call {@link #setScreenOnWhilePlaying}.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING = 35;
-
-    /**
-     * The start of the methods which have separate call complete callback.
-     * @hide
-     */
-    public static final int SEPARATE_CALL_COMPLETED_CALLBACK_START = 1000;
-
-    /** The player just completed a call {@link #notifyWhenCommandLabelReached}.
-     * @see EventCallback#onCommandLabelReached
-     * @hide
-     */
-    public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
-            SEPARATE_CALL_COMPLETED_CALLBACK_START;
-
-    /** The player just completed a call {@link #prepareDrm}.
-     * @see DrmEventCallback#onDrmPrepared
-     * @hide
-     */
-    public static final int CALL_COMPLETED_PREPARE_DRM =
-            SEPARATE_CALL_COMPLETED_CALLBACK_START + 1;
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "CALL_COMPLETED", value = {
-            CALL_COMPLETED_ATTACH_AUX_EFFECT,
-            CALL_COMPLETED_DESELECT_TRACK,
-            CALL_COMPLETED_LOOP_CURRENT,
-            CALL_COMPLETED_PAUSE,
-            CALL_COMPLETED_PLAY,
-            CALL_COMPLETED_PREPARE,
-            CALL_COMPLETED_SEEK_TO,
-            CALL_COMPLETED_SELECT_TRACK,
-            CALL_COMPLETED_SET_AUDIO_ATTRIBUTES,
-            CALL_COMPLETED_SET_AUDIO_SESSION_ID,
-            CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL,
-            CALL_COMPLETED_SET_DATA_SOURCE,
-            CALL_COMPLETED_SET_NEXT_DATA_SOURCE,
-            CALL_COMPLETED_SET_NEXT_DATA_SOURCES,
-            CALL_COMPLETED_SET_PLAYBACK_PARAMS,
-            CALL_COMPLETED_SET_PLAYER_VOLUME,
-            CALL_COMPLETED_SET_SURFACE,
-            CALL_COMPLETED_SET_SYNC_PARAMS,
-            CALL_COMPLETED_SKIP_TO_NEXT,
-            CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES,
-            CALL_COMPLETED_SET_BUFFERING_PARAMS,
-            CALL_COMPLETED_SET_DISPLAY,
-            CALL_COMPLETED_SET_WAKE_LOCK,
-            CALL_COMPLETED_SET_SCREEN_ON_WHILE_PLAYING,
-            CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED,
-            CALL_COMPLETED_PREPARE_DRM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CallCompleted {}
-
-    /** Status code represents that call is completed without an error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_NO_ERROR = 0;
-
-    /** Status code represents that call is ended with an unknown error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE;
-
-    /** Status code represents that the player is not in valid state for the operation.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_INVALID_OPERATION = 1;
-
-    /** Status code represents that the argument is illegal.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_BAD_VALUE = 2;
-
-    /** Status code represents that the operation is not allowed.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_PERMISSION_DENIED = 3;
-
-    /** Status code represents a file or network related operation error.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_ERROR_IO = 4;
-
-    /** Status code represents that the call has been skipped. For example, a {@link #seekTo}
-     * request may be skipped if it is followed by another {@link #seekTo} request.
-     * @see EventCallback#onCallCompleted
-     */
-    public static final int CALL_STATUS_SKIPPED = 5;
-
-    /** Status code represents that DRM operation is called before preparing a DRM scheme through
-     *  {@code prepareDrm}.
-     * @see EventCallback#onCallCompleted
-     */
-    // TODO: change @code to @link when DRM is unhidden
-    public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
-
-    /**
-     * @hide
-     */
-    @IntDef(flag = false, prefix = "CALL_STATUS", value = {
-            CALL_STATUS_NO_ERROR,
-            CALL_STATUS_ERROR_UNKNOWN,
-            CALL_STATUS_INVALID_OPERATION,
-            CALL_STATUS_BAD_VALUE,
-            CALL_STATUS_PERMISSION_DENIED,
-            CALL_STATUS_ERROR_IO,
-            CALL_STATUS_SKIPPED,
-            CALL_STATUS_NO_DRM_SCHEME})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface CallStatus {}
-
-    // Modular DRM begin
-
-    /**
-     * An immutable structure per {@link DataSourceDesc} with settings required to initiate a DRM
-     * protected playback session.
-     *
-     * @see DrmPreparationInfo.Builder
-     */
-    public static final class DrmPreparationInfo {
-
-        /**
-         * Mutable builder to create a {@link MediaPlayer2.DrmPreparationInfo} object.
-         *
-         * {@link Builder#Builder(UUID) UUID} must not be null; {@link #setKeyType keyType}
-         * must be one of {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
-         * <p>
-         * When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_STREAMING},
-         * {@link #setInitData(byte[]) initData} and {@link #setMimeType(String) mimeType}
-         * must not be null; When {@link #setKeyType keyType} is {@link MediaDrm#KEY_TYPE_OFFLINE},
-         * {@link #setKeySetId(byte[]) keySetId} must not be null.
-         */
-        public static final class Builder {
-
-            private final UUID mUUID;
-            private byte[] mKeySetId;
-            private byte[] mInitData;
-            private String mMimeType;
-            private int mKeyType;
-            private Map<String, String> mOptionalParameters;
-
-            /**
-             * @param uuid UUID of the crypto scheme selected to decrypt content. An UUID can be
-             * retrieved from the source listening to {@link DrmEventCallback#onDrmInfo}.
-             */
-            public Builder(@NonNull UUID uuid) {
-                this.mUUID = uuid;
-            }
-
-            /**
-             * Set identifier of a persisted offline key obtained from
-             * {@link MediaPlayer2.DrmEventCallback#onDrmPrepared}.
-             *
-             * A {@code keySetId} can be used to restore persisted offline keys into a new playback
-             * session of a DRM protected data source. When {@code keySetId} is set,
-             * {@code initData}, {@code mimeType}, {@code keyType}, {@code optionalParameters} are
-             * ignored.
-             *
-             * @param keySetId identifier of a persisted offline key
-             * @return this
-             */
-            public @NonNull Builder setKeySetId(@Nullable byte[] keySetId) {
-                this.mKeySetId = keySetId;
-                return this;
-            }
-
-            /**
-             * Set container-specific DRM initialization data. Its meaning is interpreted based on
-             * {@code mimeType}. For example, it could contain the content ID, key ID or other data
-             * obtained from the content metadata that is required to generate a
-             * {@link MediaDrm.KeyRequest}.
-             *
-             * @param initData container-specific DRM initialization data
-             * @return this
-             */
-            public @NonNull Builder setInitData(@Nullable byte[] initData) {
-                this.mInitData = initData;
-                return this;
-            }
-
-            /**
-             * Set mime type of the content
-             *
-             * @param mimeType mime type to the content
-             * @return this
-             */
-            public @NonNull Builder setMimeType(@Nullable String mimeType) {
-                this.mMimeType = mimeType;
-                return this;
-            }
-
-            /**
-             * Set type of the key request. The request may be to acquire keys
-             * for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content,
-             * {@link MediaDrm#KEY_TYPE_OFFLINE}. Releasing previously acquired keys
-             * ({@link MediaDrm#KEY_TYPE_RELEASE}) is not allowed.
-             *
-             * @param keyType type of the key request
-             * @return this
-             */
-            public @NonNull Builder setKeyType(@MediaPlayer2.MediaDrmKeyType int keyType) {
-                this.mKeyType = keyType;
-                return this;
-            }
-
-            /**
-             * Set optional parameters to be included in a {@link MediaDrm.KeyRequest} message sent
-             * to the license server.
-             *
-             * @param optionalParameters optional parameters to be included in a key request
-             * @return this
-             */
-            public @NonNull Builder setOptionalParameters(
-                    @Nullable Map<String, String> optionalParameters) {
-                this.mOptionalParameters = optionalParameters;
-                return this;
-            }
-
-            /**
-             * @return an immutable {@link DrmPreparationInfo} based on settings of this builder
-             */
-            @NonNull
-            public DrmPreparationInfo build() {
-                final DrmPreparationInfo info = new DrmPreparationInfo(mUUID, mKeySetId, mInitData,
-                        mMimeType, mKeyType, mOptionalParameters);
-                if (!info.isValid()) {
-                    throw new IllegalArgumentException("invalid DrmPreparationInfo");
-                }
-                return info;
-            }
-
-        }
-
-        private final UUID mUUID;
-        private final byte[] mKeySetId;
-        private final byte[] mInitData;
-        private final String mMimeType;
-        private final int mKeyType;
-        private final Map<String, String> mOptionalParameters;
-
-        private DrmPreparationInfo(UUID mUUID, byte[] mKeySetId, byte[] mInitData, String mMimeType,
-                int mKeyType, Map<String, String> optionalParameters) {
-            this.mUUID = mUUID;
-            this.mKeySetId = mKeySetId;
-            this.mInitData = mInitData;
-            this.mMimeType = mMimeType;
-            this.mKeyType = mKeyType;
-            this.mOptionalParameters = optionalParameters;
-        }
-
-        boolean isValid() {
-            if (mUUID == null) {
-                return false;
-            }
-            if (mKeySetId != null) {
-                // offline restore case
-                return true;
-            }
-            if (mInitData != null && mMimeType != null) {
-                // new streaming license case
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * @return UUID of the crypto scheme selected to decrypt content.
-         */
-        @NonNull
-        public UUID getUuid() {
-            return mUUID;
-        }
-
-        /**
-         * @return identifier of the persisted offline key.
-         */
-        @Nullable
-        public byte[] getKeySetId() {
-            return mKeySetId;
-        }
-
-        /**
-         * @return container-specific DRM initialization data.
-         */
-        @Nullable
-        public byte[] getInitData() {
-            return mInitData;
-        }
-
-        /**
-         * @return mime type of the content
-         */
-        @Nullable
-        public String getMimeType() {
-            return mMimeType;
-        }
-
-        /**
-         * @return type of the key request.
-         */
-        @MediaPlayer2.MediaDrmKeyType
-        public int getKeyType() {
-            return mKeyType;
-        }
-
-        /**
-         * @return optional parameters to be included in the {@link MediaDrm.KeyRequest}.
-         */
-        @Nullable
-        public Map<String, String> getOptionalParameters() {
-            return mOptionalParameters;
-        }
-    }
-
-    /**
-     * Interface definition for callbacks to be invoked when the player has the corresponding
-     * DRM events.
-     */
-    public static abstract class DrmEventCallback {
-
-        /**
-         * Called to indicate DRM info is available. Return a {@link DrmPreparationInfo} object that
-         * bundles DRM initialization parameters.
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param dsd the {@link DataSourceDesc} of this data source
-         * @param drmInfo DRM info of the source including PSSH, and subset of crypto schemes
-         *        supported by this device
-         * @return a {@link DrmPreparationInfo} object to initialize DRM playback, or null to skip
-         *         DRM initialization
-         */
-        @Nullable
-        public abstract DrmPreparationInfo onDrmInfo(@NonNull MediaPlayer2 mp,
-                @NonNull DataSourceDesc dsd, @NonNull DrmInfo drmInfo);
-
-        /**
-         * Called to give the app the opportunity to configure DRM before the session is created.
-         *
-         * This facilitates configuration of the properties, like 'securityLevel', which
-         * has to be set after DRM scheme creation but before the DRM session is opened.
-         *
-         * The only allowed DRM calls in this listener are
-         * {@link MediaDrm#getPropertyString(String)},
-         * {@link MediaDrm#getPropertyByteArray(String)},
-         * {@link MediaDrm#setPropertyString(String, String)},
-         * {@link MediaDrm#setPropertyByteArray(String, byte[])},
-         * {@link MediaDrm#setOnExpirationUpdateListener},
-         * and {@link MediaDrm#setOnKeyStatusChangeListener}.
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param dsd the {@link DataSourceDesc} of this data source
-         * @param drm handle to get/set DRM properties and listeners for this data source
-         */
-        public void onDrmConfig(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @NonNull MediaDrm drm) { }
-
-        /**
-         * Called to indicate the DRM session for {@code dsd} is ready for key request/response
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param dsd the {@link DataSourceDesc} of this data source
-         * @param request a {@link MediaDrm.KeyRequest} prepared using the
-         *        {@link DrmPreparationInfo} returned from
-         *        {@link #onDrmInfo(MediaPlayer2, DataSourceDesc, DrmInfo)}
-         * @return the response to {@code request} (from license server); returning {@code null} or
-         *         throwing an {@link RuntimeException} from this callback would trigger an
-         *         {@link EventCallback#onError}.
-         */
-        @NonNull
-        public abstract byte[] onDrmKeyRequest(@NonNull MediaPlayer2 mp,
-                @NonNull DataSourceDesc dsd, @NonNull MediaDrm.KeyRequest request);
-
-        /**
-         * Called to notify the client that {@code mp} is ready to decrypt DRM protected data source
-         * {@code dsd} or if there is an error during DRM preparation
-         *
-         * @param mp the {@code MediaPlayer2} associated with this callback
-         * @param dsd the {@link DataSourceDesc} of this data source
-         * @param status the result of DRM preparation.
-         * @param keySetId optional identifier that can be used to restore DRM playback initiated
-         *        with a {@link MediaDrm#KEY_TYPE_OFFLINE} key request.
-         *
-         * @see DrmPreparationInfo.Builder#setKeySetId(byte[])
-         */
-        public void onDrmPrepared(@NonNull MediaPlayer2 mp, @NonNull DataSourceDesc dsd,
-                @PrepareDrmStatusCode int status, @Nullable byte[] keySetId) { }
-
-    }
-
-    private final Object mDrmEventCallbackLock = new Object();
-    private Pair<Executor, DrmEventCallback> mDrmEventCallback;
-
-    /**
-     * Registers the callback to be invoked for various DRM events.
-     *
-     * This is a synchronous call.
-     *
-     * @param eventCallback the callback that will be run
-     * @param executor the executor through which the callback should be invoked
-     */
-    public void setDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull DrmEventCallback eventCallback) {
-        if (eventCallback == null) {
-            throw new IllegalArgumentException("Illegal null EventCallback");
-        }
-        if (executor == null) {
-            throw new IllegalArgumentException(
-                    "Illegal null Executor for the EventCallback");
-        }
-        synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallback = new Pair<Executor, DrmEventCallback>(executor, eventCallback);
-        }
-    }
-
-    /**
-     * Clear the {@link DrmEventCallback}.
-     *
-     * This is a synchronous call.
-     */
-    public void clearDrmEventCallback() {
-        synchronized (mDrmEventCallbackLock) {
-            mDrmEventCallback = null;
-        }
-    }
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * DRM preparation has succeeded.
-     */
-    public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * The device required DRM provisioning but couldn't reach the provisioning server.
-     */
-    public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * The device required DRM provisioning but the provisioning server denied the request.
-     */
-    public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * The DRM preparation has failed .
-     */
-    public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * The crypto scheme UUID is not supported by the device.
-     */
-    public static final int PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME = 4;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * The hardware resources are not available, due to being in use.
-     */
-    public static final int PREPARE_DRM_STATUS_RESOURCE_BUSY = 5;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * Restoring persisted offline keys failed.
-     */
-    public static final int PREPARE_DRM_STATUS_RESTORE_ERROR = 6;
-
-    /**
-     * A status code for {@link DrmEventCallback#onDrmPrepared} listener.
-     * <p>
-     *
-     * Error during key request/response exchange with license server.
-     */
-    public static final int PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR = 7;
-
-    /** @hide */
-    @IntDef(flag = false, prefix = "PREPARE_DRM_STATUS", value = {
-        PREPARE_DRM_STATUS_SUCCESS,
-        PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
-        PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
-        PREPARE_DRM_STATUS_PREPARATION_ERROR,
-        PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME,
-        PREPARE_DRM_STATUS_RESOURCE_BUSY,
-        PREPARE_DRM_STATUS_RESTORE_ERROR,
-        PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PrepareDrmStatusCode {}
-
-    /** @hide */
-    @IntDef({
-        MediaDrm.KEY_TYPE_STREAMING,
-        MediaDrm.KEY_TYPE_OFFLINE,
-        MediaDrm.KEY_TYPE_RELEASE,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaDrmKeyType {}
-
-    /** @hide */
-    @StringDef({
-        MediaDrm.PROPERTY_VENDOR,
-        MediaDrm.PROPERTY_VERSION,
-        MediaDrm.PROPERTY_DESCRIPTION,
-        MediaDrm.PROPERTY_ALGORITHMS,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface MediaDrmStringProperty {}
-
-    /**
-     * Retrieves the DRM Info associated with the given source
-     *
-     * @param dsd The DRM protected data source
-     *
-     * @throws IllegalStateException if called before being prepared
-     * @hide
-     */
-    @TestApi
-    public DrmInfo getDrmInfo(@NonNull DataSourceDesc dsd) {
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            DrmInfo drmInfo = null;
-
-            // there is not much point if the app calls getDrmInfo within an OnDrmInfoListener;
-            // regardless below returns drmInfo anyway instead of raising an exception
-            synchronized (sourceInfo) {
-                if (!sourceInfo.mDrmInfoResolved && sourceInfo.mDrmInfo == null) {
-                    final String msg = "The Player has not been prepared yet";
-                    Log.v(TAG, msg);
-                    throw new IllegalStateException(msg);
-                }
-
-                if (sourceInfo.mDrmInfo != null) {
-                    drmInfo  = sourceInfo.mDrmInfo.makeCopy();
-                }
-            }   // synchronized
-
-            return drmInfo;
-        }
-        return null;
-    }
-
-    /**
-     * Prepares the DRM for the given data source
-     * <p>
-     * If {@link DrmEventCallback} is registered, it will be called during
-     * preparation to allow configuration of the DRM properties before opening the
-     * DRM session. It should be used only for a series of
-     * {@link #getDrmPropertyString(DataSourceDesc, String)} and
-     * {@link #setDrmPropertyString(DataSourceDesc, String, String)} calls
-     * and refrain from any lengthy operation.
-     * <p>
-     * If the device has not been provisioned before, this call also provisions the device
-     * which involves accessing the provisioning server and can take a variable time to
-     * complete depending on the network connectivity.
-     * When needed, the provisioning will be launched  in the background.
-     * The listener {@link DrmEventCallback#onDrmPrepared}
-     * will be called when provisioning and preparation are finished. The application should
-     * check the status code returned with {@link DrmEventCallback#onDrmPrepared} to proceed.
-     * <p>
-     * The registered {@link DrmEventCallback#onDrmPrepared} is called to indicate the DRM
-     * session being ready. The application should not make any assumption about its call
-     * sequence (e.g., before or after prepareDrm returns).
-     * <p>
-     *
-     * @param dsd The DRM protected data source
-     *
-     * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
-     * from the source listening to {@link DrmEventCallback#onDrmInfo}.
-     *
-     * @return a token which can be used to cancel the operation later with {@link #cancelCommand}.
-     * @hide
-     */
-    // This is an asynchronous call.
-    @TestApi
-    public @NonNull Object prepareDrm(@NonNull DataSourceDesc dsd, @NonNull UUID uuid) {
-        return addTask(newPrepareDrmTask(dsd, uuid));
-    }
-
-    private Task newPrepareDrmTask(DataSourceDesc dsd, UUID uuid) {
-        return new Task(CALL_COMPLETED_PREPARE_DRM, true) {
-            @Override
-            void process() {
-                final SourceInfo sourceInfo = getSourceInfo(dsd);
-                int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                boolean finishPrepare = true;
-
-                if (sourceInfo == null) {
-                    Log.e(TAG, "prepareDrm(): DataSource not found.");
-                } else if (sourceInfo.mDrmInfo == null) {
-                    // only allowing if tied to a protected source;
-                    // might relax for releasing offline keys
-                    Log.e(TAG, "prepareDrm(): Wrong usage: The player must be prepared and "
-                            + "DRM info be retrieved before this call.");
-                } else {
-                    status = PREPARE_DRM_STATUS_SUCCESS;
-                }
-
-                try {
-                    if (status == PREPARE_DRM_STATUS_SUCCESS) {
-                        sourceInfo.mDrmHandle.prepare(uuid);
-                    }
-                } catch (ResourceBusyException e) {
-                    status = PREPARE_DRM_STATUS_RESOURCE_BUSY;
-                } catch (UnsupportedSchemeException e) {
-                    status = PREPARE_DRM_STATUS_UNSUPPORTED_SCHEME;
-                } catch (NotProvisionedException e) {
-                    Log.w(TAG, "prepareDrm: NotProvisionedException");
-
-                    // handle provisioning internally; it'll reset mPrepareDrmInProgress
-                    status = sourceInfo.mDrmHandle.handleProvisioninig(uuid, mTaskId);
-
-                    if (status == PREPARE_DRM_STATUS_SUCCESS) {
-                        // License will be setup in provisioning
-                        finishPrepare = false;
-                    } else {
-                        synchronized (sourceInfo.mDrmHandle) {
-                            sourceInfo.mDrmHandle.cleanDrmObj();
-                        }
-
-                        switch (status) {
-                            case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
-                                Log.e(TAG, "prepareDrm: Provisioning was required but failed "
-                                        + "due to a network error.");
-                                break;
-
-                            case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
-                                Log.e(TAG, "prepareDrm: Provisioning was required but the request "
-                                        + "was denied by the server.");
-                                break;
-
-                            case PREPARE_DRM_STATUS_PREPARATION_ERROR:
-                            default:
-                                Log.e(TAG, "prepareDrm: Post-provisioning preparation failed.");
-                                break;
-                        }
-                    }
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-
-                if (finishPrepare) {
-                    sourceInfo.mDrmHandle.finishPrepare(status);
-                    synchronized (mTaskLock) {
-                        mCurrentTask = null;
-                        processPendingTask_l();
-                    }
-                }
-
-            }
-        };
-    }
-
-    /**
-     * Releases the DRM session for the given data source
-     * <p>
-     * The player has to have an active DRM session and be in stopped, or prepared
-     * state before this call is made.
-     * A {@link #reset()} call will release the DRM session implicitly.
-     *
-     * @param dsd The DRM protected data source
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session to release
-     * @hide
-     */
-    // This is a synchronous call.
-    @TestApi
-    public void releaseDrm(@NonNull DataSourceDesc dsd)
-            throws NoDrmSchemeException {
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            sourceInfo.mDrmHandle.release();
-        }
-    }
-
-    private native void native_releaseDrm(long mSrcId);
-
-    /**
-     * A key request/response exchange occurs between the app and a license server
-     * to obtain or release keys used to decrypt the given data source.
-     * <p>
-     * {@code getDrmKeyRequest()} is used to obtain an opaque key request byte array that is
-     * delivered to the license server.  The opaque key request byte array is returned
-     * in KeyRequest.data.  The recommended URL to deliver the key request to is
-     * returned in {@code KeyRequest.defaultUrl}.
-     * <p>
-     * After the app has received the key request response from the server,
-     * it should deliver to the response to the DRM engine plugin using the method
-     * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
-     *
-     * @param dsd the DRM protected data source
-     *
-     * @param keySetId is the key-set identifier of the offline keys being released when keyType is
-     * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
-     * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
-     *
-     * @param initData is the container-specific initialization data when the keyType is
-     * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
-     * interpreted based on the mime type provided in the mimeType parameter.  It could
-     * contain, for example, the content ID, key ID or other data obtained from the content
-     * metadata that is required in generating the key request.
-     * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
-     *
-     * @param mimeType identifies the mime type of the content
-     *
-     * @param keyType specifies the type of the request. The request may be to acquire
-     * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
-     * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
-     * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
-     *
-     * @param optionalParameters are included in the key request message to
-     * allow a client application to provide additional message parameters to the server.
-     * This may be {@code null} if no additional parameters are to be sent.
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @hide
-     */
-    @TestApi
-    public MediaDrm.KeyRequest getDrmKeyRequest(
-            @NonNull DataSourceDesc dsd,
-            @Nullable byte[] keySetId, @Nullable byte[] initData,
-            @Nullable String mimeType, @MediaDrmKeyType int keyType,
-            @Nullable Map<String, String> optionalParameters)
-            throws NoDrmSchemeException {
-        Log.v(TAG, "getDrmKeyRequest: " +
-                " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
-                " keyType: " + keyType + " optionalParameters: " + optionalParameters);
-
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            return sourceInfo.mDrmHandle.getDrmKeyRequest(
-                    keySetId, initData, mimeType, keyType, optionalParameters);
-        }
-        return null;
-    }
-
-    /**
-     * A key response is received from the license server by the app for the given DRM protected
-     * data source, then provided to the DRM engine plugin using {@code provideDrmKeyResponse}.
-     * <p>
-     * When the response is for an offline key request, a key-set identifier is returned that
-     * can be used to later restore the keys to a new session with the method
-     * {@link #restoreDrmKeys(DataSourceDesc, byte[])}.
-     * When the response is for a streaming or release request, null is returned.
-     *
-     * @param dsd the DRM protected data source
-     *
-     * @param keySetId When the response is for a release request, keySetId identifies the saved
-     * key associated with the release request (i.e., the same keySetId passed to the earlier
-     * {@link # getDrmKeyRequest(DataSourceDesc, byte[], byte[], String, int, Map)} call).
-     * It MUST be null when the response is for either streaming or offline key requests.
-     *
-     * @param response the byte array response from the server
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @throws DeniedByServerException if the response indicates that the
-     * server rejected the request
-     * @hide
-     */
-    // This is a synchronous call.
-    @TestApi
-    public byte[] provideDrmKeyResponse(
-            @NonNull DataSourceDesc dsd,
-            @Nullable byte[] keySetId, @NonNull byte[] response)
-            throws NoDrmSchemeException, DeniedByServerException {
-        Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId + " response: " + response);
-
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            return sourceInfo.mDrmHandle.provideDrmKeyResponse(keySetId, response);
-        }
-        return null;
-    }
-
-    /**
-     * Restore persisted offline keys into a new session for the given DRM protected data source.
-     * {@code keySetId} identifies the keys to load, obtained from a prior call to
-     * {@link #provideDrmKeyResponse(DataSourceDesc, byte[], byte[])}.
-     *
-     * @param dsd the DRM protected data source
-     *
-     * @param keySetId identifies the saved key set to restore
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @hide
-     */
-    // This is a synchronous call.
-    @TestApi
-    public void restoreDrmKeys(
-            @NonNull DataSourceDesc dsd,
-            @NonNull byte[] keySetId)
-            throws NoDrmSchemeException {
-        Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId);
-
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            sourceInfo.mDrmHandle.restoreDrmKeys(keySetId);
-        }
-    }
-
-    /**
-     * Read a DRM engine plugin String property value, given the DRM protected data source
-     * and property name string.
-     *
-     * @param dsd the DRM protected data source
-     *
-     * @param propertyName the property name
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @hide
-     */
-    @TestApi
-    public String getDrmPropertyString(
-            @NonNull DataSourceDesc dsd,
-            @NonNull @MediaDrmStringProperty String propertyName)
-            throws NoDrmSchemeException {
-        Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
-
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            return sourceInfo.mDrmHandle.getDrmPropertyString(propertyName);
-        }
-        return null;
-    }
-
-    /**
-     * Set a DRM engine plugin String property value for the given data source.
-     *
-     * @param dsd the DRM protected data source
-     * @param propertyName the property name
-     * @param value the property value
-     *
-     * Standard fields names are:
-     * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
-     * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
-     *
-     * @throws NoDrmSchemeException if there is no active DRM session
-     * @hide
-     */
-    // This is a synchronous call.
-    @TestApi
-    public void setDrmPropertyString(
-            @NonNull DataSourceDesc dsd,
-            @NonNull @MediaDrmStringProperty String propertyName, @NonNull String value)
-            throws NoDrmSchemeException {
-        // TODO: this implementation only works when dsd is the only data source
-        Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
-
-        final SourceInfo sourceInfo = getSourceInfo(dsd);
-        if (sourceInfo != null) {
-            sourceInfo.mDrmHandle.setDrmPropertyString(propertyName, value);
-        }
-    }
-
-    /**
-     * Encapsulates the DRM properties of the source.
-     */
-    public static final class DrmInfo {
-        private Map<UUID, byte[]> mMapPssh;
-        private UUID[] mSupportedSchemes;
-
-        /**
-         * Returns the PSSH info of the data source for each supported DRM scheme.
-         */
-        public @NonNull Map<UUID, byte[]> getPssh() {
-            return mMapPssh;
-        }
-
-        /**
-         * Returns the intersection of the data source and the device DRM schemes.
-         * It effectively identifies the subset of the source's DRM schemes which
-         * are supported by the device too.
-         */
-        public @NonNull List<UUID> getSupportedSchemes() {
-            return Arrays.asList(mSupportedSchemes);
-        }
-
-        private DrmInfo(Map<UUID, byte[]> pssh, UUID[] supportedSchemes) {
-            mMapPssh = pssh;
-            mSupportedSchemes = supportedSchemes;
-        }
-
-        private static DrmInfo create(PlayerMessage msg) {
-            Log.v(TAG, "DrmInfo.create(" + msg + ")");
-
-            Iterator<Value> in = msg.getValuesList().iterator();
-            byte[] pssh = in.next().getBytesValue().toByteArray();
-
-            Log.v(TAG, "DrmInfo.create() PSSH: " + DrmInfo.arrToHex(pssh));
-            Map<UUID, byte[]> mapPssh = DrmInfo.parsePSSH(pssh, pssh.length);
-            Log.v(TAG, "DrmInfo.create() PSSH: " + mapPssh);
-
-            int supportedDRMsCount = in.next().getInt32Value();
-            UUID[] supportedSchemes = new UUID[supportedDRMsCount];
-            for (int i = 0; i < supportedDRMsCount; i++) {
-                byte[] uuid = new byte[16];
-                in.next().getBytesValue().copyTo(uuid, 0);
-
-                supportedSchemes[i] = DrmInfo.bytesToUUID(uuid);
-
-                Log.v(TAG, "DrmInfo() supportedScheme[" + i + "]: " + supportedSchemes[i]);
-            }
-
-            Log.v(TAG, "DrmInfo.create() psshsize: " + pssh.length
-                    + " supportedDRMsCount: " + supportedDRMsCount);
-            return new DrmInfo(mapPssh, supportedSchemes);
-        }
-
-        private DrmInfo makeCopy() {
-            return new DrmInfo(this.mMapPssh, this.mSupportedSchemes);
-        }
-
-        private static String arrToHex(byte[] bytes) {
-            String out = "0x";
-            for (int i = 0; i < bytes.length; i++) {
-                out += String.format("%02x", bytes[i]);
-            }
-
-            return out;
-        }
-
-        private static UUID bytesToUUID(byte[] uuid) {
-            long msb = 0, lsb = 0;
-            for (int i = 0; i < 8; i++) {
-                msb |= (((long) uuid[i]     & 0xff) << (8 * (7 - i)));
-                lsb |= (((long) uuid[i + 8] & 0xff) << (8 * (7 - i)));
-            }
-
-            return new UUID(msb, lsb);
-        }
-
-        private static Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
-            Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
-
-            final int uuidSize = 16;
-            final int dataLenSize = 4;
-
-            int len = psshsize;
-            int numentries = 0;
-            int i = 0;
-
-            while (len > 0) {
-                if (len < uuidSize) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
-                                             + "UUID: (%d < 16) pssh: %d", len, psshsize));
-                    return null;
-                }
-
-                byte[] subset = Arrays.copyOfRange(pssh, i, i + uuidSize);
-                UUID uuid = bytesToUUID(subset);
-                i += uuidSize;
-                len -= uuidSize;
-
-                // get data length
-                if (len < 4) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
-                                             + "datalen: (%d < 4) pssh: %d", len, psshsize));
-                    return null;
-                }
-
-                subset = Arrays.copyOfRange(pssh, i, i + dataLenSize);
-                int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
-                        ? ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16)
-                        | ((subset[1] & 0xff) <<  8) |  (subset[0] & 0xff)        :
-                        ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16)
-                        | ((subset[2] & 0xff) <<  8) |  (subset[3] & 0xff);
-                i += dataLenSize;
-                len -= dataLenSize;
-
-                if (len < datalen) {
-                    Log.w(TAG, String.format("parsePSSH: len is too short to parse "
-                                             + "data: (%d < %d) pssh: %d", len, datalen, psshsize));
-                    return null;
-                }
-
-                byte[] data = Arrays.copyOfRange(pssh, i, i + datalen);
-
-                // skip the data
-                i += datalen;
-                len -= datalen;
-
-                Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
-                                         numentries, uuid, arrToHex(data), psshsize));
-                numentries++;
-                result.put(uuid, data);
-            }
-
-            return result;
-        }
-    };  // DrmInfo
-
-    /**
-     * Thrown when a DRM method is called when there is no active DRM session.
-     * Extends MediaDrm.MediaDrmException
-     */
-    public static final class NoDrmSchemeException extends MediaDrmException {
-        public NoDrmSchemeException(@Nullable String detailMessage) {
-            super(detailMessage);
-        }
-    }
-
-    private native void native_prepareDrm(
-            long srcId, @NonNull byte[] uuid, @NonNull byte[] drmSessionId);
-
-    // Instantiated from the native side
-    @SuppressWarnings("unused")
-    private static class StreamEventCallback extends AudioTrack.StreamEventCallback {
-        public long mJAudioTrackPtr;
-        public long mNativeCallbackPtr;
-        public long mUserDataPtr;
-
-        StreamEventCallback(long jAudioTrackPtr, long nativeCallbackPtr, long userDataPtr) {
-            super();
-            mJAudioTrackPtr = jAudioTrackPtr;
-            mNativeCallbackPtr = nativeCallbackPtr;
-            mUserDataPtr = userDataPtr;
-        }
-
-        @Override
-        public void onTearDown(AudioTrack track) {
-            native_stream_event_onTearDown(mNativeCallbackPtr, mUserDataPtr);
-        }
-
-        @Override
-        public void onPresentationEnded(AudioTrack track) {
-            native_stream_event_onStreamPresentationEnd(mNativeCallbackPtr, mUserDataPtr);
-        }
-
-        @Override
-        public void onDataRequest(AudioTrack track, int size) {
-            native_stream_event_onStreamDataRequest(
-                    mJAudioTrackPtr, mNativeCallbackPtr, mUserDataPtr);
-        }
-    }
-
-    /**
-     * Returns a byte[] containing the remainder of 'in', closing it when done.
-     */
-    private static byte[] readInputStreamFully(InputStream in) throws IOException {
-        try {
-            return readInputStreamFullyNoClose(in);
-        } finally {
-            in.close();
-        }
-    }
-
-    /**
-     * Returns a byte[] containing the remainder of 'in'.
-     */
-    private static byte[] readInputStreamFullyNoClose(InputStream in) throws IOException {
-        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1024];
-        int count;
-        while ((count = in.read(buffer)) != -1) {
-            bytes.write(buffer, 0, count);
-        }
-        return bytes.toByteArray();
-    }
-
-    private static byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
-        long msb = uuid.getMostSignificantBits();
-        long lsb = uuid.getLeastSignificantBits();
-
-        byte[] uuidBytes = new byte[16];
-        for (int i = 0; i < 8; ++i) {
-            uuidBytes[i] = (byte) (msb >>> (8 * (7 - i)));
-            uuidBytes[8 + i] = (byte) (lsb >>> (8 * (7 - i)));
-        }
-
-        return uuidBytes;
-    }
-
-    private static class TimedTextUtil {
-        // These keys must be in sync with the keys in TextDescription2.h
-        private static final int KEY_START_TIME                     = 7; // int
-        private static final int KEY_STRUCT_TEXT_POS               = 14; // TextPos
-        private static final int KEY_STRUCT_TEXT                   = 16; // Text
-        private static final int KEY_GLOBAL_SETTING               = 101;
-        private static final int KEY_LOCAL_SETTING                = 102;
-
-        private static TimedText parsePlayerMessage(PlayerMessage playerMsg) {
-            if (playerMsg.getValuesCount() == 0) {
-                return null;
-            }
-
-            String textChars = null;
-            Rect textBounds = null;
-            Iterator<Value> in = playerMsg.getValuesList().iterator();
-            int type = in.next().getInt32Value();
-            if (type == KEY_LOCAL_SETTING) {
-                type = in.next().getInt32Value();
-                if (type != KEY_START_TIME) {
-                    return null;
-                }
-                int startTimeMs = in.next().getInt32Value();
-
-                type = in.next().getInt32Value();
-                if (type != KEY_STRUCT_TEXT) {
-                    return null;
-                }
-
-                byte[] text = in.next().getBytesValue().toByteArray();
-                if (text == null || text.length == 0) {
-                    textChars = null;
-                } else {
-                    textChars = new String(text);
-                }
-
-            } else if (type != KEY_GLOBAL_SETTING) {
-                Log.w(TAG, "Invalid timed text key found: " + type);
-                return null;
-            }
-            if (in.hasNext()) {
-                type = in.next().getInt32Value();
-                if (type == KEY_STRUCT_TEXT_POS) {
-                    int top = in.next().getInt32Value();
-                    int left = in.next().getInt32Value();
-                    int bottom = in.next().getInt32Value();
-                    int right = in.next().getInt32Value();
-                    textBounds = new Rect(left, top, right, bottom);
-                }
-            }
-            return null;
-            /* TimedText c-tor usage is temporarily commented out.
-             * TODO(b/117527789): use SUBTITLE path for MEDIA_MIMETYPE_TEXT_3GPP track
-             *                    and remove TimedText path from MediaPlayer2.
-            return new TimedText(textChars, textBounds);
-            */
-        }
-    }
-
-    private Object addTask(Task task) {
-        synchronized (mTaskLock) {
-            mPendingTasks.add(task);
-            processPendingTask_l();
-        }
-        return task;
-    }
-
-    @GuardedBy("mTaskLock")
-    private void processPendingTask_l() {
-        if (mCurrentTask != null) {
-            return;
-        }
-        if (!mPendingTasks.isEmpty()) {
-            Task task = mPendingTasks.remove(0);
-            mCurrentTask = task;
-            mTaskHandler.post(task);
-        }
-    }
-
-    private abstract class Task implements Runnable {
-        final long mTaskId = mTaskIdGenerator.getAndIncrement();
-        private final int mMediaCallType;
-        private final boolean mNeedToWaitForEventToComplete;
-        private DataSourceDesc mDSD;
-
-        Task(int mediaCallType, boolean needToWaitForEventToComplete) {
-            mMediaCallType = mediaCallType;
-            mNeedToWaitForEventToComplete = needToWaitForEventToComplete;
-        }
-
-        abstract void process() throws IOException, NoDrmSchemeException;
-
-        @Override
-        public void run() {
-            int status = CALL_STATUS_NO_ERROR;
-            try {
-                if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
-                        && getState() == PLAYER_STATE_ERROR) {
-                    status = CALL_STATUS_INVALID_OPERATION;
-                } else {
-                    if (mMediaCallType == CALL_COMPLETED_SEEK_TO) {
-                        synchronized (mTaskLock) {
-                            if (!mPendingTasks.isEmpty()) {
-                                Task nextTask = mPendingTasks.get(0);
-                                if (nextTask.mMediaCallType == mMediaCallType) {
-                                    throw new CommandSkippedException(
-                                            "consecutive seekTo is skipped except last one");
-                                }
-                            }
-                        }
-                    }
-                    process();
-                }
-            } catch (IllegalStateException e) {
-                status = CALL_STATUS_INVALID_OPERATION;
-            } catch (IllegalArgumentException e) {
-                status = CALL_STATUS_BAD_VALUE;
-            } catch (SecurityException e) {
-                status = CALL_STATUS_PERMISSION_DENIED;
-            } catch (IOException e) {
-                status = CALL_STATUS_ERROR_IO;
-            } catch (NoDrmSchemeException e) {
-                status = CALL_STATUS_NO_DRM_SCHEME;
-            } catch (CommandSkippedException e) {
-                status = CALL_STATUS_SKIPPED;
-            } catch (Exception e) {
-                status = CALL_STATUS_ERROR_UNKNOWN;
-            }
-            mDSD = getCurrentDataSource();
-
-            if (mMediaCallType != CALL_COMPLETED_SEEK_TO) {
-                synchronized (mTaskLock) {
-                    mIsPreviousCommandSeekTo = false;
-                }
-            }
-
-            // TODO: Make native implementations asynchronous and let them send notifications.
-            if (!mNeedToWaitForEventToComplete || status != CALL_STATUS_NO_ERROR) {
-
-                sendCompleteNotification(status);
-
-                synchronized (mTaskLock) {
-                    mCurrentTask = null;
-                    processPendingTask_l();
-                }
-            }
-        }
-
-        private void sendCompleteNotification(int status) {
-            // In {@link #notifyWhenCommandLabelReached} case, a separate callback
-            // {@link #onCommandLabelReached} is already called in {@code process()}.
-            // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared
-            if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
-                    || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) {
-                return;
-            }
-            sendEvent(new EventNotifier() {
-                @Override
-                public void notify(EventCallback callback) {
-                    callback.onCallCompleted(
-                            MediaPlayer2.this, mDSD, mMediaCallType, status);
-                }
-            });
-        }
-    };
-
-    private final class CommandSkippedException extends RuntimeException {
-        CommandSkippedException(String detailMessage) {
-            super(detailMessage);
-        }
-    };
-
-    // Modular DRM
-    private final Map<UUID, MediaDrm> mDrmObjs = Collections.synchronizedMap(new HashMap<>());
-    private class DrmHandle {
-
-        static final int PROVISION_TIMEOUT_MS = 60000;
-
-        final DataSourceDesc mDSD;
-        final long mSrcId;
-
-        //--- guarded by |this| start
-        MediaDrm mDrmObj;
-        byte[] mDrmSessionId;
-        UUID mActiveDrmUUID;
-        boolean mDrmConfigAllowed;
-        boolean mDrmProvisioningInProgress;
-        boolean mPrepareDrmInProgress;
-        Future<?> mProvisionResult;
-        DrmPreparationInfo mPrepareInfo;
-        //--- guarded by |this| end
-
-        DrmHandle(DataSourceDesc dsd, long srcId) {
-            mDSD = dsd;
-            mSrcId = srcId;
-        }
-
-        void prepare(UUID uuid) throws UnsupportedSchemeException,
-                ResourceBusyException, NotProvisionedException, InterruptedException,
-                ExecutionException, TimeoutException {
-            Log.v(TAG, "prepareDrm: uuid: " + uuid);
-
-            synchronized (this) {
-                if (mActiveDrmUUID != null) {
-                    final String msg = "prepareDrm(): Wrong usage: There is already "
-                            + "an active DRM scheme with " + uuid;
-                    Log.e(TAG, msg);
-                    throw new IllegalStateException(msg);
-                }
-
-                if (mPrepareDrmInProgress) {
-                    final String msg = "prepareDrm(): Wrong usage: There is already "
-                            + "a pending prepareDrm call.";
-                    Log.e(TAG, msg);
-                    throw new IllegalStateException(msg);
-                }
-
-                if (mDrmProvisioningInProgress) {
-                    final String msg = "prepareDrm(): Unexpectd: Provisioning already in progress";
-                    Log.e(TAG, msg);
-                    throw new IllegalStateException(msg);
-                }
-
-                // shouldn't need this; just for safeguard
-                cleanDrmObj();
-
-                mPrepareDrmInProgress = true;
-
-                try {
-                    // only creating the DRM object to allow pre-openSession configuration
-                    prepareDrm_createDrmStep(uuid);
-                } catch (Exception e) {
-                    Log.w(TAG, "prepareDrm(): Exception ", e);
-                    mPrepareDrmInProgress = false;
-                    throw e;
-                }
-
-                mDrmConfigAllowed = true;
-            }  // synchronized
-
-            // call the callback outside the lock
-            sendDrmEventWait(new DrmEventNotifier<Void>() {
-                @Override
-                public Void notifyWait(DrmEventCallback callback) {
-                    callback.onDrmConfig(MediaPlayer2.this, mDSD, mDrmObj);
-                    return null;
-                }
-            });
-
-            synchronized (this) {
-                mDrmConfigAllowed = false;
-                boolean earlyExit = false;
-
-                try {
-                    prepareDrm_openSessionStep(uuid);
-
-                    this.mActiveDrmUUID = uuid;
-                    mPrepareDrmInProgress = false;
-                } catch (IllegalStateException e) {
-                    final String msg = "prepareDrm(): Wrong usage: The player must be "
-                            + "in the prepared state to call prepareDrm().";
-                    Log.e(TAG, msg);
-                    earlyExit = true;
-                    mPrepareDrmInProgress = false;
-                    throw new IllegalStateException(msg);
-                } catch (NotProvisionedException e) {
-                    Log.w(TAG, "prepareDrm: NotProvisionedException", e);
-                    throw e;
-                } catch (Exception e) {
-                    Log.e(TAG, "prepareDrm: Exception " + e);
-                    earlyExit = true;
-                    mPrepareDrmInProgress = false;
-                    throw e;
-                } finally {
-                    if (earlyExit) {  // clean up object if didn't succeed
-                        cleanDrmObj();
-                    }
-                }  // finally
-            }  // synchronized
-        }
-
-        void prepareDrm_createDrmStep(UUID uuid)
-                throws UnsupportedSchemeException {
-            Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
-
-            try {
-                mDrmObj = mDrmObjs.computeIfAbsent(uuid, scheme -> {
-                    try {
-                        return new MediaDrm(scheme);
-                    } catch (UnsupportedSchemeException e) {
-                        throw new IllegalArgumentException(e);
-                    }
-                });
-                Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
-            } catch (Exception e) { // UnsupportedSchemeException
-                Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
-                throw e;
-            }
-        }
-
-        void prepareDrm_openSessionStep(UUID uuid)
-                throws NotProvisionedException, ResourceBusyException {
-            Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
-
-            // TODO:
-            // don't need an open session for a future specialKeyReleaseDrm mode but we should do
-            // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
-            // at prepareDrm/openSession rather than getDrmKeyRequest/provideDrmKeyResponse
-            try {
-                mDrmSessionId = mDrmObj.openSession();
-                Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
-
-                // Sending it down to native/mediaserver to create the crypto object
-                // This call could simply fail due to bad player state, e.g., after play().
-                final MediaPlayer2 mp2 = MediaPlayer2.this;
-                mp2.native_prepareDrm(mSrcId, getByteArrayFromUUID(uuid), mDrmSessionId);
-                Log.v(TAG, "prepareDrm_openSessionStep: native_prepareDrm/Crypto succeeded");
-
-            } catch (Exception e) { //ResourceBusyException, NotProvisionedException
-                Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
-                throw e;
-            }
-
-        }
-
-        int handleProvisioninig(UUID uuid, long taskId) {
-            synchronized (this) {
-                if (mDrmProvisioningInProgress) {
-                    Log.e(TAG, "handleProvisioninig: Unexpected mDrmProvisioningInProgress");
-                    return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-
-                MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
-                if (provReq == null) {
-                    Log.e(TAG, "handleProvisioninig: getProvisionRequest returned null.");
-                    return PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-
-                Log.v(TAG, "handleProvisioninig provReq "
-                        + " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
-
-                // networking in a background thread
-                mDrmProvisioningInProgress = true;
-
-                mProvisionResult = sDrmThreadPool.submit(newProvisioningTask(uuid, taskId));
-
-                return PREPARE_DRM_STATUS_SUCCESS;
-            }
-        }
-
-        void provision(UUID uuid, long taskId) {
-
-            MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
-            String urlStr = provReq.getDefaultUrl();
-            urlStr += "&signedRequest=" + new String(provReq.getData());
-            Log.v(TAG, "handleProvisioninig: Thread is initialised url: " + urlStr);
-
-            byte[] response = null;
-            boolean provisioningSucceeded = false;
-            int status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
-            try {
-                URL url = new URL(urlStr);
-                final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-                try {
-                    connection.setRequestMethod("POST");
-                    connection.setDoOutput(false);
-                    connection.setDoInput(true);
-                    connection.setConnectTimeout(PROVISION_TIMEOUT_MS);
-                    connection.setReadTimeout(PROVISION_TIMEOUT_MS);
-
-                    connection.connect();
-                    response = readInputStreamFully(connection.getInputStream());
-
-                    Log.v(TAG, "handleProvisioninig: Thread run: response " +
-                            response.length + " " + response);
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                    Log.w(TAG, "handleProvisioninig: Thread run: connect " + e + " url: " + url);
-                } finally {
-                    connection.disconnect();
-                }
-            } catch (Exception e)   {
-                status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
-                Log.w(TAG, "handleProvisioninig: Thread run: openConnection " + e);
-            }
-
-            if (response != null) {
-                try {
-                    mDrmObj.provideProvisionResponse(response);
-                    Log.v(TAG, "handleProvisioninig: Thread run: " +
-                            "provideProvisionResponse SUCCEEDED!");
-
-                    provisioningSucceeded = true;
-                } catch (Exception e) {
-                    status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
-                    Log.w(TAG, "handleProvisioninig: Thread run: " +
-                            "provideProvisionResponse " + e);
-                }
-            }
-
-            boolean succeeded = false;
-
-            synchronized (this) {
-                // continuing with prepareDrm
-                if (provisioningSucceeded) {
-                    succeeded = resumePrepare(uuid);
-                    status = (succeeded) ?
-                            PREPARE_DRM_STATUS_SUCCESS :
-                            PREPARE_DRM_STATUS_PREPARATION_ERROR;
-                }
-                mDrmProvisioningInProgress = false;
-                mPrepareDrmInProgress = false;
-                if (!succeeded) {
-                    cleanDrmObj();  // cleaning up if it hasn't gone through while in the lock
-                }
-            }  // synchronized
-
-            // calling the callback outside the lock
-            finishPrepare(status);
-
-            synchronized (mTaskLock) {
-                if (mCurrentTask != null
-                        && mCurrentTask.mTaskId == taskId
-                        && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE_DRM
-                        && mCurrentTask.mNeedToWaitForEventToComplete) {
-                    mCurrentTask = null;
-                    processPendingTask_l();
-                }
-            }
-        }
-
-        Runnable newProvisioningTask(UUID uuid, long taskId) {
-            return new Runnable() {
-                @Override
-                public void run() {
-                    provision(uuid, taskId);
-                }
-            };
-        }
-
-        boolean resumePrepare(UUID uuid) {
-            Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
-
-            // mDrmLock is guaranteed to be held
-            boolean success = false;
-            try {
-                // resuming
-                prepareDrm_openSessionStep(uuid);
-
-                this.mActiveDrmUUID = uuid;
-
-                success = true;
-            } catch (Exception e) {
-                Log.w(TAG, "handleProvisioninig: Thread run native_prepareDrm resume failed:" + e);
-                // mDrmObj clean up is done by the caller
-            }
-
-            return success;
-        }
-
-        synchronized boolean setPreparationInfo(DrmPreparationInfo prepareInfo) {
-            if (prepareInfo == null || !prepareInfo.isValid() || mPrepareInfo != null) {
-                return false;
-            }
-            mPrepareInfo = prepareInfo;
-            return true;
-        }
-
-        void finishPrepare(int status) {
-            if (status != PREPARE_DRM_STATUS_SUCCESS) {
-                notifyPrepared(status, null);
-                return;
-            }
-
-            if (mPrepareInfo == null) {
-                // Deprecated: this can only happen when using MediaPlayer Version 1 APIs
-                notifyPrepared(status, null);
-                return;
-            }
-
-            final byte[] keySetId = mPrepareInfo.mKeySetId;
-            if (keySetId != null) {
-                try {
-                    mDrmObj.restoreKeys(mDrmSessionId, keySetId);
-                    notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
-                } catch (Exception e) {
-                    notifyPrepared(PREPARE_DRM_STATUS_RESTORE_ERROR, keySetId);
-                }
-                return;
-            }
-
-            sDrmThreadPool.submit(newKeyExchangeTask());
-        }
-
-        Runnable newKeyExchangeTask() {
-            return new Runnable() {
-                @Override
-                public void run() {
-                    final byte[] initData = mPrepareInfo.mInitData;
-                    final String mimeType = mPrepareInfo.mMimeType;
-                    final int keyType = mPrepareInfo.mKeyType;
-                    final Map<String, String> optionalParams = mPrepareInfo.mOptionalParameters;
-                    byte[] keySetId = null;
-                    try {
-                        KeyRequest req;
-                        req = getDrmKeyRequest(null, initData, mimeType, keyType, optionalParams);
-                        byte[] response = sendDrmEventWait(new DrmEventNotifier<byte[]>() {
-                            @Override
-                            public byte[] notifyWait(DrmEventCallback callback) {
-                                final MediaPlayer2 mp = MediaPlayer2.this;
-                                return callback.onDrmKeyRequest(mp, mDSD, req);
-                            }
-                        });
-                        keySetId = provideDrmKeyResponse(null, response);
-                    } catch (Exception e) {
-                    }
-                    if (keySetId == null) {
-                        notifyPrepared(PREPARE_DRM_STATUS_KEY_EXCHANGE_ERROR, null);
-                    } else {
-                        notifyPrepared(PREPARE_DRM_STATUS_SUCCESS, keySetId);
-                    }
-                }
-            };
-        }
-
-        void notifyPrepared(final int status, byte[] keySetId) {
-
-            Message msg;
-            if (status == PREPARE_DRM_STATUS_SUCCESS) {
-                msg = mTaskHandler.obtainMessage(
-                        MEDIA_DRM_PREPARED, 0, 0, null);
-            } else {
-                msg = mTaskHandler.obtainMessage(
-                        MEDIA_ERROR, status, MEDIA_ERROR_UNKNOWN, null);
-            }
-            mTaskHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mTaskHandler.handleMessage(msg, mSrcId);
-                }
-            });
-
-            sendDrmEvent(new DrmEventNotifier() {
-                @Override
-                public void notify(DrmEventCallback callback) {
-                    callback.onDrmPrepared(MediaPlayer2.this, mDSD, status,
-                            keySetId);
-                }
-            });
-
-        }
-
-        void cleanDrmObj() {
-            // the caller holds mDrmLock
-            Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
-
-            if (mDrmSessionId != null)    {
-                mDrmObj.closeSession(mDrmSessionId);
-                mDrmSessionId = null;
-            }
-        }
-
-        void release() throws NoDrmSchemeException {
-            synchronized (this) {
-                Log.v(TAG, "releaseDrm:");
-
-                if (mActiveDrmUUID == null) {
-                    Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
-                    throw new NoDrmSchemeException(
-                            "releaseDrm: No active DRM scheme to release.");
-                }
-
-                try {
-                    // we don't have the player's state in this layer. The below call raises
-                    // exception if we're in a non-stopped/prepared state.
-
-                    // for cleaning native/mediaserver crypto object
-                    native_releaseDrm(mSrcId);
-
-                    // for cleaning client-side MediaDrm object; only called if above has succeeded
-                    cleanDrmObj();
-
-                    this.mActiveDrmUUID = null;
-                } catch (IllegalStateException e) {
-                    Log.w(TAG, "releaseDrm: Exception ", e);
-                    throw new IllegalStateException(
-                            "releaseDrm: The player is not in a valid state.");
-                } catch (Exception e) {
-                    Log.e(TAG, "releaseDrm: Exception ", e);
-                }
-            }  // synchronized
-        }
-
-        void cleanup() {
-            synchronized (this) {
-                Log.v(TAG, "cleanupDrm: " +
-                        " mProvisioningTask=" + mProvisionResult +
-                        " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
-                        " mActiveDrmScheme=" + mActiveDrmUUID);
-
-                if (mProvisionResult != null) {
-                    // timeout; relying on HttpUrlConnection
-                    try {
-                        mProvisionResult.get();
-                    }
-                    catch (InterruptedException | ExecutionException e) {
-                        Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
-                    }
-                }
-
-                // set to false to avoid duplicate release calls
-                this.mActiveDrmUUID = null;
-
-                native_releaseDrm(mSrcId);
-                cleanDrmObj();
-            }   // synchronized
-        }
-
-        Runnable newCleanupTask() {
-            return new Runnable() {
-                @Override
-                public void run() {
-                    cleanup();
-                }
-            };
-        }
-
-        MediaDrm.KeyRequest getDrmKeyRequest(
-                byte[] keySetId, byte[] initData,
-                String mimeType, int keyType,
-                Map<String, String> optionalParameters)
-                throws NoDrmSchemeException {
-            synchronized (this) {
-                if (mActiveDrmUUID == null) {
-                    Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                    throw new NoDrmSchemeException(
-                            "getDrmKeyRequest: Has to set a DRM scheme first.");
-                }
-
-                try {
-                    byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
-                            mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                            keySetId;                  // keySetId for KEY_TYPE_RELEASE
-
-                    HashMap<String, String> hmapOptionalParameters =
-                            (optionalParameters != null)
-                            ? new HashMap<String, String>(optionalParameters)
-                            : null;
-
-                    MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(
-                            scope, initData, mimeType, keyType, hmapOptionalParameters);
-                    Log.v(TAG, "getDrmKeyRequest:   --> request: " + request);
-
-                    return request;
-
-                } catch (NotProvisionedException e) {
-                    Log.w(TAG, "getDrmKeyRequest NotProvisionedException: " +
-                            "Unexpected. Shouldn't have reached here.");
-                    throw new IllegalStateException("getDrmKeyRequest: provisioning error.");
-                } catch (Exception e) {
-                    Log.w(TAG, "getDrmKeyRequest Exception " + e);
-                    throw e;
-                }
-
-            }
-        }
-
-        byte[] provideDrmKeyResponse(byte[] keySetId, byte[] response)
-                throws NoDrmSchemeException, DeniedByServerException {
-            synchronized (this) {
-
-                if (mActiveDrmUUID == null) {
-                    Log.e(TAG, "getDrmKeyRequest NoDrmSchemeException");
-                    throw new NoDrmSchemeException(
-                            "getDrmKeyRequest: Has to set a DRM scheme first.");
-                }
-
-                try {
-                    byte[] scope = (keySetId == null) ?
-                                    mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
-                                    keySetId;                  // keySetId for KEY_TYPE_RELEASE
-
-                    byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
-
-                    Log.v(TAG, "provideDrmKeyResponse: keySetId: " + keySetId
-                            + " response: " + response + " --> " + keySetResult);
-
-
-                    return keySetResult;
-
-                } catch (NotProvisionedException e) {
-                    Log.w(TAG, "provideDrmKeyResponse NotProvisionedException: " +
-                            "Unexpected. Shouldn't have reached here.");
-                    throw new IllegalStateException("provideDrmKeyResponse: " +
-                            "Unexpected provisioning error.");
-                } catch (Exception e) {
-                    Log.w(TAG, "provideDrmKeyResponse Exception " + e);
-                    throw e;
-                }
-            }
-        }
-
-        void restoreDrmKeys(byte[] keySetId)
-                throws NoDrmSchemeException {
-            synchronized (this) {
-                if (mActiveDrmUUID == null) {
-                    Log.w(TAG, "restoreDrmKeys NoDrmSchemeException");
-                    throw new NoDrmSchemeException(
-                            "restoreDrmKeys: Has to set a DRM scheme first.");
-                }
-
-                try {
-                    mDrmObj.restoreKeys(mDrmSessionId, keySetId);
-                } catch (Exception e) {
-                    Log.w(TAG, "restoreKeys Exception " + e);
-                    throw e;
-                }
-            }
-        }
-
-        String getDrmPropertyString(String propertyName)
-                throws NoDrmSchemeException {
-            String v;
-            synchronized (this) {
-
-                if (mActiveDrmUUID == null && !mDrmConfigAllowed) {
-                    Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
-                    throw new NoDrmSchemeException(
-                            "getDrmPropertyString: Has to prepareDrm() first.");
-                }
-
-                try {
-                    v = mDrmObj.getPropertyString(propertyName);
-                } catch (Exception e) {
-                    Log.w(TAG, "getDrmPropertyString Exception " + e);
-                    throw e;
-                }
-            }   // synchronized
-
-            Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + v);
-
-            return v;
-        }
-
-        void setDrmPropertyString(String propertyName, String value)
-                throws NoDrmSchemeException {
-            synchronized (this) {
-
-                if ( mActiveDrmUUID == null && !mDrmConfigAllowed ) {
-                    Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
-                    throw new NoDrmSchemeException(
-                            "setDrmPropertyString: Has to prepareDrm() first.");
-                }
-
-                try {
-                    mDrmObj.setPropertyString(propertyName, value);
-                } catch ( Exception e ) {
-                    Log.w(TAG, "setDrmPropertyString Exception " + e);
-                    throw e;
-                }
-            }
-        }
-
-    }
-
-    final class SourceInfo {
-        final DataSourceDesc mDSD;
-        final long mId = mSrcIdGenerator.getAndIncrement();
-        AtomicInteger mBufferedPercentage = new AtomicInteger(0);
-        boolean mClosed = false;
-        int mPrepareBarrier = 1;
-
-        // m*AsNextSource (below) only applies to pending data sources in the playlist;
-        // the meanings of mCurrentSourceInfo.{mStateAsNextSource,mPlayPendingAsNextSource}
-        // are undefined.
-        int mStateAsNextSource = NEXT_SOURCE_STATE_INIT;
-        boolean mPlayPendingAsNextSource = false;
-
-        // Modular DRM
-        final DrmHandle mDrmHandle;
-        DrmInfo mDrmInfo;
-        boolean mDrmInfoResolved;
-
-        SourceInfo(DataSourceDesc dsd) {
-            this.mDSD = dsd;
-            mDrmHandle = new DrmHandle(dsd, mId);
-        }
-
-        void close() {
-            synchronized (this) {
-                if (!mClosed) {
-                    if (mDSD != null) {
-                        mDSD.close();
-                    }
-                    mClosed = true;
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return String.format("%s(%d)", SourceInfo.class.getName(), mId);
-        }
-
-    }
-
-    private SourceInfo getSourceInfo(long srcId) {
-        synchronized (mSrcLock) {
-            if (isCurrentSource(srcId)) {
-                return mCurrentSourceInfo;
-            }
-            if (isNextSource(srcId)) {
-                return mNextSourceInfos.peek();
-            }
-        }
-        return null;
-    }
-
-    private SourceInfo getSourceInfo(DataSourceDesc dsd) {
-        synchronized (mSrcLock) {
-            if (isCurrentSource(dsd)) {
-                return mCurrentSourceInfo;
-            }
-            if (isNextSource(dsd)) {
-                return mNextSourceInfos.peek();
-            }
-        }
-        return null;
-    }
-
-    private boolean isCurrentSource(long srcId) {
-        synchronized (mSrcLock) {
-            return mCurrentSourceInfo != null && mCurrentSourceInfo.mId == srcId;
-        }
-    }
-
-    private boolean isCurrentSource(DataSourceDesc dsd) {
-        synchronized (mSrcLock) {
-            return mCurrentSourceInfo != null && mCurrentSourceInfo.mDSD == dsd;
-        }
-    }
-
-    private boolean isNextSource(long srcId) {
-        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-        return nextSourceInfo != null && nextSourceInfo.mId == srcId;
-    }
-
-    private boolean isNextSource(DataSourceDesc dsd) {
-        SourceInfo nextSourceInfo = mNextSourceInfos.peek();
-        return nextSourceInfo != null && nextSourceInfo.mDSD == dsd;
-    }
-
-    @GuardedBy("mSrcLock")
-    private void setCurrentSourceInfo_l(SourceInfo sourceInfo) {
-        cleanupSourceInfo(mCurrentSourceInfo);
-        mCurrentSourceInfo = sourceInfo;
-    }
-
-    @GuardedBy("mSrcLock")
-    private void clearNextSourceInfos_l() {
-        while (!mNextSourceInfos.isEmpty()) {
-            cleanupSourceInfo(mNextSourceInfos.poll());
-        }
-    }
-
-    private void cleanupSourceInfo(SourceInfo sourceInfo) {
-        if (sourceInfo != null) {
-            sourceInfo.close();
-            Runnable task = sourceInfo.mDrmHandle.newCleanupTask();
-            sDrmThreadPool.submit(task);
-        }
-    }
-
-    private void clearSourceInfos() {
-        synchronized (mSrcLock) {
-            setCurrentSourceInfo_l(null);
-            clearNextSourceInfos_l();
-        }
-    }
-
-    public static final class MetricsConstants {
-        private MetricsConstants() {}
-
-        /**
-         * Key to extract the MIME type of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
-
-        /**
-         * Key to extract the codec being used to decode the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
-
-        /**
-         * Key to extract the width (in pixels) of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String WIDTH = "android.media.mediaplayer.width";
-
-        /**
-         * Key to extract the height (in pixels) of the video track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String HEIGHT = "android.media.mediaplayer.height";
-
-        /**
-         * Key to extract the count of video frames played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES = "android.media.mediaplayer.frames";
-
-        /**
-         * Key to extract the count of video frames dropped
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
-
-        /**
-         * Key to extract the MIME type of the audio track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
-
-        /**
-         * Key to extract the codec being used to decode the audio track
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a String.
-         */
-        public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
-
-        /**
-         * Key to extract the duration (in milliseconds) of the
-         * media being played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String DURATION = "android.media.mediaplayer.durationMs";
-
-        /**
-         * Key to extract the playing time (in milliseconds) of the
-         * media being played
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is a long.
-         */
-        public static final String PLAYING = "android.media.mediaplayer.playingMs";
-
-        /**
-         * Key to extract the count of errors encountered while
-         * playing the media
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERRORS = "android.media.mediaplayer.err";
-
-        /**
-         * Key to extract an (optional) error code detected while
-         * playing the media
-         * from the {@link MediaPlayer2#getMetrics} return value.
-         * The value is an integer.
-         */
-        public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
-
-    }
-
-    private void keepAudioSessionIdAlive(int sessionId) {
-        synchronized (mSessionIdLock) {
-            if (mDummyAudioTrack != null) {
-                if (mDummyAudioTrack.getAudioSessionId() == sessionId) {
-                    return;
-                }
-                mDummyAudioTrack.release();
-            }
-            // TODO: parameters can be optimized
-            mDummyAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
-                    AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, 2,
-                    AudioTrack.MODE_STATIC, sessionId);
-        }
-    }
-
-    private void keepAudioSessionIdAlive(AudioTrack at) {
-        synchronized (mSessionIdLock) {
-            if (mDummyAudioTrack != null) {
-                if (mDummyAudioTrack.getAudioSessionId() == at.getAudioSessionId()) {
-                    at.release();
-                    return;
-                }
-                mDummyAudioTrack.release();
-            }
-            mDummyAudioTrack = at;
-        }
-    }
-}
diff --git a/media/apex/java/android/media/MediaPlayer2Utils.java b/media/apex/java/android/media/MediaPlayer2Utils.java
deleted file mode 100644
index ac34260..0000000
--- a/media/apex/java/android/media/MediaPlayer2Utils.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 android.media;
-
-/**
- * Helper class used by native code to reduce JNI calls from native side.
- * @hide
- */
-public class MediaPlayer2Utils {
-    /**
-     * Returns whether audio offloading is supported for the given audio format.
-     *
-     * @param encoding the type of encoding defined in {@link AudioFormat}
-     * @param sampleRate the sampling rate of the stream
-     * @param channelMask the channel mask defined in {@link AudioFormat}
-     */
-    // @CalledByNative
-    public static boolean isOffloadedAudioPlaybackSupported(
-            int encoding, int sampleRate, int channelMask) {
-        final AudioFormat format = new AudioFormat.Builder()
-                .setEncoding(encoding)
-                .setSampleRate(sampleRate)
-                .setChannelMask(channelMask)
-                .build();
-        //TODO MP2 needs to pass AudioAttributes for this query, instead of using default attr
-        return AudioManager.isOffloadedPlaybackSupported(format,
-                (new AudioAttributes.Builder()).build());
-    }
-}
diff --git a/media/apex/java/android/media/UriDataSourceDesc.java b/media/apex/java/android/media/UriDataSourceDesc.java
deleted file mode 100644
index adf7a7d..0000000
--- a/media/apex/java/android/media/UriDataSourceDesc.java
+++ /dev/null
@@ -1,80 +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.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.Uri;
-
-import java.net.HttpCookie;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Structure of data source descriptor for sources using URI.
- *
- * Used by {@link MediaPlayer2#setDataSource}, {@link MediaPlayer2#setNextDataSource} and
- * {@link MediaPlayer2#setNextDataSources} to set data source for playback.
- *
- * <p>Users should use {@link Builder} to change {@link UriDataSourceDesc}.
- * @hide
- */
-public class UriDataSourceDesc extends DataSourceDesc {
-    private Uri mUri;
-    private Map<String, String> mHeader;
-    private List<HttpCookie> mCookies;
-
-    UriDataSourceDesc(String mediaId, long startPositionMs, long endPositionMs,
-            Uri uri, Map<String, String> header, List<HttpCookie> cookies) {
-        super(mediaId, startPositionMs, endPositionMs);
-        mUri = uri;
-        mHeader = header;
-        mCookies = cookies;
-    }
-
-    /**
-     * Return the Uri of this data source.
-     * @return the Uri of this data source
-     */
-    public @NonNull Uri getUri() {
-        return mUri;
-    }
-
-    /**
-     * Return the Uri headers of this data source.
-     * @return the Uri headers of this data source
-     */
-    public @Nullable Map<String, String> getHeaders() {
-        if (mHeader == null) {
-            return null;
-        }
-        return new HashMap<String, String>(mHeader);
-    }
-
-    /**
-     * Return the Uri cookies of this data source.
-     * @return the Uri cookies of this data source
-     */
-    public @Nullable List<HttpCookie> getCookies() {
-        if (mCookies == null) {
-            return null;
-        }
-        return new ArrayList<HttpCookie>(mCookies);
-    }
-}
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..53babcb 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;
@@ -493,6 +494,19 @@
     // See http://www.exiv2.org/makernote.html#R11
     private static final int PEF_MAKER_NOTE_SKIP_SIZE = 6;
 
+    // See PNG (Portable Network Graphics) Specification, Version 1.2,
+    // 3.1. PNG file signature
+    private static final byte[] PNG_SIGNATURE = new byte[] {(byte) 0x89, (byte) 0x50, (byte) 0x4e,
+            (byte) 0x47, (byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a};
+    // See PNG (Portable Network Graphics) Specification, Version 1.2,
+    // 3.7. eXIf Exchangeable Image File (Exif) Profile
+    private static final byte[] PNG_CHUNK_TYPE_EXIF = new byte[]{(byte) 0x65, (byte) 0x58,
+            (byte) 0x49, (byte) 0x66};
+    private static final byte[] PNG_CHUNK_TYPE_IEND = new byte[]{(byte) 0x49, (byte) 0x45,
+            (byte) 0x4e, (byte) 0x44};
+    private static final int PNG_CHUNK_LENGTH_BYTE_LENGTH = 4;
+    private static final int PNG_CHUNK_CRC_BYTE_LENGTH = 4;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private static SimpleDateFormat sFormatter;
     private static SimpleDateFormat sFormatterTz;
@@ -1310,6 +1324,7 @@
     private static final int IMAGE_TYPE_RW2 = 10;
     private static final int IMAGE_TYPE_SRW = 11;
     private static final int IMAGE_TYPE_HEIF = 12;
+    private static final int IMAGE_TYPE_PNG = 13;
 
     static {
         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
@@ -1451,6 +1466,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.
      *
@@ -1779,6 +1825,10 @@
                     getRw2Attributes(inputStream);
                     break;
                 }
+                case IMAGE_TYPE_PNG: {
+                    getPngAttributes(inputStream);
+                    break;
+                }
                 case IMAGE_TYPE_ARW:
                 case IMAGE_TYPE_CR2:
                 case IMAGE_TYPE_DNG:
@@ -1992,6 +2042,7 @@
             if (in.skip(mThumbnailOffset) != mThumbnailOffset) {
                 throw new IOException("Corrupted image");
             }
+            // TODO: Need to handle potential OutOfMemoryError
             byte[] buffer = new byte[mThumbnailLength];
             if (in.read(buffer) != mThumbnailLength) {
                 throw new IOException("Corrupted image");
@@ -2331,6 +2382,8 @@
             return IMAGE_TYPE_ORF;
         } else if (isRw2Format(signatureCheckBytes)) {
             return IMAGE_TYPE_RW2;
+        } else if (isPngFormat(signatureCheckBytes)) {
+            return IMAGE_TYPE_PNG;
         }
         // Certain file formats (PEF) are identified in readImageFileDirectory()
         return IMAGE_TYPE_UNKNOWN;
@@ -2446,16 +2499,24 @@
      * http://fileformats.archiveteam.org/wiki/Olympus_ORF
      */
     private boolean isOrfFormat(byte[] signatureCheckBytes) throws IOException {
-        ByteOrderedDataInputStream signatureInputStream =
-                new ByteOrderedDataInputStream(signatureCheckBytes);
-        // Read byte order
-        mExifByteOrder = readByteOrder(signatureInputStream);
-        // Set byte order
-        signatureInputStream.setByteOrder(mExifByteOrder);
+        ByteOrderedDataInputStream signatureInputStream = null;
 
-        short orfSignature = signatureInputStream.readShort();
-        if (orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2) {
-            return true;
+        try {
+            signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
+
+            // Read byte order
+            mExifByteOrder = readByteOrder(signatureInputStream);
+            // Set byte order
+            signatureInputStream.setByteOrder(mExifByteOrder);
+
+            short orfSignature = signatureInputStream.readShort();
+            return orfSignature == ORF_SIGNATURE_1 || orfSignature == ORF_SIGNATURE_2;
+        } catch (Exception e) {
+            // Do nothing
+        } finally {
+            if (signatureInputStream != null) {
+                signatureInputStream.close();
+            }
         }
         return false;
     }
@@ -2465,21 +2526,43 @@
      * See http://lclevy.free.fr/raw/
      */
     private boolean isRw2Format(byte[] signatureCheckBytes) throws IOException {
-        ByteOrderedDataInputStream signatureInputStream =
-                new ByteOrderedDataInputStream(signatureCheckBytes);
-        // Read byte order
-        mExifByteOrder = readByteOrder(signatureInputStream);
-        // Set byte order
-        signatureInputStream.setByteOrder(mExifByteOrder);
+        ByteOrderedDataInputStream signatureInputStream = null;
 
-        short signatureByte = signatureInputStream.readShort();
-        if (signatureByte == RW2_SIGNATURE) {
-            return true;
+        try {
+            signatureInputStream = new ByteOrderedDataInputStream(signatureCheckBytes);
+
+            // Read byte order
+            mExifByteOrder = readByteOrder(signatureInputStream);
+            // Set byte order
+            signatureInputStream.setByteOrder(mExifByteOrder);
+
+            short signatureByte = signatureInputStream.readShort();
+            signatureInputStream.close();
+            return signatureByte == RW2_SIGNATURE;
+        } catch (Exception e) {
+            // Do nothing
+        } finally {
+            if (signatureInputStream != null) {
+                signatureInputStream.close();
+            }
         }
         return false;
     }
 
     /**
+     * PNG's file signature is first 8 bytes.
+     * See PNG (Portable Network Graphics) Specification, Version 1.2, 3.1. PNG file signature
+     */
+    private boolean isPngFormat(byte[] signatureCheckBytes) throws IOException {
+        for (int i = 0; i < PNG_SIGNATURE.length; i++) {
+            if (signatureCheckBytes[i] != PNG_SIGNATURE[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Loads EXIF attributes from a JPEG input stream.
      *
      * @param in The input stream that starts with the JPEG data.
@@ -2553,7 +2636,7 @@
 
                         readExifSegment(value, imageType);
 
-                        // Save offset values for createJpegThumbnailBitmap() function
+                        // Save offset values for handleThumbnailFromJfif() function
                         mExifOffset = (int) offset;
                     } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) {
                         // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
@@ -2854,6 +2937,7 @@
                     throw new IOException("Invalid identifier");
                 }
 
+                // TODO: Need to handle potential OutOfMemoryError
                 byte[] bytes = new byte[length];
                 if (in.read(bytes) != length) {
                     throw new IOException("Can't read exif");
@@ -2980,6 +3064,64 @@
         }
     }
 
+    // PNG contains the EXIF data as a Special-Purpose Chunk
+    private void getPngAttributes(ByteOrderedDataInputStream in) throws IOException {
+        if (DEBUG) {
+            Log.d(TAG, "getPngAttributes starting with: " + in);
+        }
+
+        // PNG uses Big Endian by default.
+        // See PNG (Portable Network Graphics) Specification, Version 1.2,
+        // 2.1. Integers and byte order
+        in.setByteOrder(ByteOrder.BIG_ENDIAN);
+
+        // Skip the signature bytes
+        in.seek(PNG_SIGNATURE.length);
+
+        try {
+            while (true) {
+                // Each chunk is made up of four parts:
+                //   1) Length: 4-byte unsigned integer indicating the number of bytes in the
+                //   Chunk Data field. Excludes Chunk Type and CRC bytes.
+                //   2) Chunk Type: 4-byte chunk type code.
+                //   3) Chunk Data: The data bytes. Can be zero-length.
+                //   4) CRC: 4-byte data calculated on the preceding bytes in the chunk. Always
+                //   present.
+                // --> 4 (length bytes) + 4 (type bytes) + X (data bytes) + 4 (CRC bytes)
+                // See PNG (Portable Network Graphics) Specification, Version 1.2,
+                // 3.2. Chunk layout
+                int length = in.readInt();
+
+                byte[] type = new byte[PNG_CHUNK_LENGTH_BYTE_LENGTH];
+                if (in.read(type) != type.length) {
+                    throw new IOException("Encountered invalid length while parsing PNG chunk"
+                            + "type");
+                }
+
+                if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
+                    // IEND marks the end of the image.
+                    break;
+                } else if (Arrays.equals(type, PNG_CHUNK_TYPE_EXIF)) {
+                    // TODO: Need to handle potential OutOfMemoryError
+                    byte[] data = new byte[length];
+                    if (in.read(data) != length) {
+                        throw new IOException("Failed to read given length for given PNG chunk "
+                                + "type: " + byteArrayToHexString(type));
+                    }
+                    readExifSegment(data, IFD_TYPE_PRIMARY);
+                    break;
+                } else {
+                    // Skip to next chunk
+                    in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
+                }
+            }
+        } catch (EOFException e) {
+            // Should not reach here. Will only reach here if the file is corrupted or
+            // does not follow the PNG specifications
+            throw new IOException("Encountered corrupt PNG file.");
+        }
+    }
+
     // Stores a new JPEG image with EXIF attributes into a given output stream.
     private void saveJpegAttributes(InputStream inputStream, OutputStream outputStream)
             throws IOException {
@@ -3485,6 +3627,7 @@
 
                 if (mFilename == null && mAssetInputStream == null
                         && mSeekableFileDescriptor == null) {
+                    // TODO: Need to handle potential OutOfMemoryError
                     // Save the thumbnail in memory if the input doesn't support reading again.
                     byte[] thumbnailBytes = new byte[thumbnailLength];
                     in.seek(thumbnailOffset);
@@ -3518,6 +3661,7 @@
                 return;
             }
 
+            // TODO: Need to handle potential OutOfMemoryError
             // Set thumbnail byte array data for non-consecutive strip bytes
             byte[] totalStripBytes =
                     new byte[(int) Arrays.stream(stripByteCounts).sum()];
@@ -3536,6 +3680,7 @@
                 in.seek(skipBytes);
                 bytesRead += skipBytes;
 
+                // TODO: Need to handle potential OutOfMemoryError
                 // Read strip bytes
                 byte[] stripBytes = new byte[stripByteCount];
                 in.read(stripBytes);
@@ -4335,4 +4480,12 @@
         }
         return null;
     }
+
+    private static String byteArrayToHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder(bytes.length * 2);
+        for (int i = 0; i < bytes.length; i++) {
+            sb.append(String.format("%02x", bytes[i]));
+        }
+        return sb.toString();
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 84fe27d..378064d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -113,88 +113,6 @@
     ],
 }
 
-cc_library_shared {
-    name: "libmedia2_jni",
-
-    srcs: [
-        "android_media_DataSourceCallback.cpp",
-        "android_media_MediaMetricsJNI.cpp",
-        "android_media_MediaPlayer2.cpp",
-        "android_media_SyncParams.cpp",
-    ],
-
-    shared_libs: [
-        // NDK or LLNDK or NDK-compliant
-        "libandroid",
-        "libbinder_ndk",
-        "libcgrouprc",
-        "libmediandk",
-        "libmediametrics",
-        "libnativehelper_compat_libc++",
-        "liblog",
-        "libvndksupport",
-    ],
-
-    header_libs: [
-        "libhardware_headers",
-        "libnativewindow_headers",
-    ],
-
-    static_libs: [
-        // MediaCas
-        "android.hidl.allocator@1.0",
-        "android.hidl.memory@1.0",
-        "libhidlbase",
-        "libhidlmemory",
-        "libbinderthreadstate",
-
-        // MediaPlayer2 implementation
-        "libbase",
-        "libcrypto",
-        "libcutils",
-        "libjsoncpp",
-        "libmedia_player2_util",
-        "libmediaplayer2",
-        "libmediaplayer2-protos",
-        "libmediandk_utils",
-        "libmediautils",
-        "libprocessgroup",
-        "libprotobuf-cpp-lite",
-        "libstagefright_esds",
-        "libstagefright_foundation_without_imemory",
-        "libstagefright_httplive",
-        "libstagefright_id3",
-        "libstagefright_mpeg2support",
-        "libstagefright_nuplayer2",
-        "libstagefright_player2",
-        "libstagefright_rtsp_player2",
-        "libstagefright_timedtext2",
-        "libutils",
-        "libmedia2_jni_core",
-    ],
-
-    group_static_libs: true,
-
-    include_dirs: [
-        "frameworks/base/core/jni",
-        "frameworks/native/include/media/openmax",
-        "system/media/camera/include",
-    ],
-
-    export_include_dirs: ["."],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-error=deprecated-declarations",
-        "-Wunused",
-        "-Wunreachable-code",
-        "-fvisibility=hidden",
-    ],
-
-    ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
-}
-
 subdirs = [
     "audioeffect",
     "soundpool",
diff --git a/media/jni/android_media_DataSourceCallback.cpp b/media/jni/android_media_DataSourceCallback.cpp
deleted file mode 100644
index c91d409..0000000
--- a/media/jni/android_media_DataSourceCallback.cpp
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 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.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "JDataSourceCallback-JNI"
-#include <utils/Log.h>
-
-#include "android_media_DataSourceCallback.h"
-
-#include "log/log.h"
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-
-#include <drm/drm_framework_common.h>
-#include <mediaplayer2/JavaVMHelper.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <nativehelper/ScopedLocalRef.h>
-
-namespace android {
-
-static const size_t kBufferSize = 64 * 1024;
-
-JDataSourceCallback::JDataSourceCallback(JNIEnv* env, jobject source)
-    : mJavaObjStatus(OK),
-      mSizeIsCached(false),
-      mCachedSize(0) {
-    mDataSourceCallbackObj = env->NewGlobalRef(source);
-    CHECK(mDataSourceCallbackObj != NULL);
-
-    ScopedLocalRef<jclass> media2DataSourceClass(env, env->GetObjectClass(mDataSourceCallbackObj));
-    CHECK(media2DataSourceClass.get() != NULL);
-
-    mReadAtMethod = env->GetMethodID(media2DataSourceClass.get(), "readAt", "(J[BII)I");
-    CHECK(mReadAtMethod != NULL);
-    mGetSizeMethod = env->GetMethodID(media2DataSourceClass.get(), "getSize", "()J");
-    CHECK(mGetSizeMethod != NULL);
-    mCloseMethod = env->GetMethodID(media2DataSourceClass.get(), "close", "()V");
-    CHECK(mCloseMethod != NULL);
-
-    ScopedLocalRef<jbyteArray> tmp(env, env->NewByteArray(kBufferSize));
-    mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get());
-    CHECK(mByteArrayObj != NULL);
-}
-
-JDataSourceCallback::~JDataSourceCallback() {
-    JNIEnv* env = JavaVMHelper::getJNIEnv();
-    env->DeleteGlobalRef(mDataSourceCallbackObj);
-    env->DeleteGlobalRef(mByteArrayObj);
-}
-
-status_t JDataSourceCallback::initCheck() const {
-    return OK;
-}
-
-ssize_t JDataSourceCallback::readAt(off64_t offset, void *data, size_t size) {
-    Mutex::Autolock lock(mLock);
-
-    if (mJavaObjStatus != OK) {
-        return -1;
-    }
-    if (size > kBufferSize) {
-        size = kBufferSize;
-    }
-
-    JNIEnv* env = JavaVMHelper::getJNIEnv();
-    jint numread = env->CallIntMethod(mDataSourceCallbackObj, mReadAtMethod,
-            (jlong)offset, mByteArrayObj, (jint)0, (jint)size);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred in readAt()");
-        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
-        env->ExceptionClear();
-        mJavaObjStatus = UNKNOWN_ERROR;
-        return -1;
-    }
-    if (numread < 0) {
-        if (numread != -1) {
-            ALOGW("An error occurred in readAt()");
-            mJavaObjStatus = UNKNOWN_ERROR;
-            return -1;
-        } else {
-            // numread == -1 indicates EOF
-            return 0;
-        }
-    }
-    if ((size_t)numread > size) {
-        ALOGE("readAt read too many bytes.");
-        mJavaObjStatus = UNKNOWN_ERROR;
-        return -1;
-    }
-
-    ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread);
-    env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)data);
-    return numread;
-}
-
-status_t JDataSourceCallback::getSize(off64_t* size) {
-    Mutex::Autolock lock(mLock);
-
-    if (mJavaObjStatus != OK) {
-        return UNKNOWN_ERROR;
-    }
-    if (mSizeIsCached) {
-        *size = mCachedSize;
-        return OK;
-    }
-
-    JNIEnv* env = JavaVMHelper::getJNIEnv();
-    *size = env->CallLongMethod(mDataSourceCallbackObj, mGetSizeMethod);
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred in getSize()");
-        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
-        env->ExceptionClear();
-        // After returning an error, size shouldn't be used by callers.
-        *size = UNKNOWN_ERROR;
-        mJavaObjStatus = UNKNOWN_ERROR;
-        return UNKNOWN_ERROR;
-    }
-
-    // The minimum size should be -1, which indicates unknown size.
-    if (*size < 0) {
-        *size = -1;
-    }
-
-    mCachedSize = *size;
-    mSizeIsCached = true;
-    return OK;
-}
-
-void JDataSourceCallback::close() {
-    Mutex::Autolock lock(mLock);
-
-    JNIEnv* env = JavaVMHelper::getJNIEnv();
-    env->CallVoidMethod(mDataSourceCallbackObj, mCloseMethod);
-    // The closed state is effectively the same as an error state.
-    mJavaObjStatus = UNKNOWN_ERROR;
-}
-
-String8 JDataSourceCallback::toString() {
-    return String8::format("JDataSourceCallback(pid %d, uid %d)", getpid(), getuid());
-}
-
-String8 JDataSourceCallback::getMIMEType() const {
-    return String8("application/octet-stream");
-}
-
-}  // namespace android
diff --git a/media/jni/android_media_DataSourceCallback.h b/media/jni/android_media_DataSourceCallback.h
deleted file mode 100644
index 5bde682..0000000
--- a/media/jni/android_media_DataSourceCallback.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 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.
- */
-
-#ifndef _ANDROID_MEDIA_DATASOURCECALLBACK_H_
-#define _ANDROID_MEDIA_DATASOURCECALLBACK_H_
-
-#include "jni.h"
-
-#include <media/DataSource.h>
-#include <media/stagefright/foundation/ABase.h>
-#include <utils/Errors.h>
-#include <utils/Mutex.h>
-
-namespace android {
-
-// The native counterpart to a Java android.media.DataSourceCallback. It inherits from
-// DataSource.
-//
-// If the java DataSource returns an error or throws an exception it
-// will be considered to be in a broken state, and the only further call this
-// will make is to close().
-class JDataSourceCallback : public DataSource {
-public:
-    JDataSourceCallback(JNIEnv *env, jobject source);
-    virtual ~JDataSourceCallback();
-
-    virtual status_t initCheck() const override;
-    virtual ssize_t readAt(off64_t offset, void *data, size_t size) override;
-    virtual status_t getSize(off64_t *size) override;
-
-    virtual String8 toString() override;
-    virtual String8 getMIMEType() const override;
-    virtual void close() override;
-private:
-    // Protect all member variables with mLock because this object will be
-    // accessed on different threads.
-    Mutex mLock;
-
-    // The status of the java DataSource. Set to OK unless an error occurred or
-    // close() was called.
-    status_t mJavaObjStatus;
-    // Only call the java getSize() once so the app can't change the size on us.
-    bool mSizeIsCached;
-    off64_t mCachedSize;
-
-    jobject mDataSourceCallbackObj;
-    jmethodID mReadAtMethod;
-    jmethodID mGetSizeMethod;
-    jmethodID mCloseMethod;
-    jbyteArray mByteArrayObj;
-
-    DISALLOW_EVIL_CONSTRUCTORS(JDataSourceCallback);
-};
-
-}  // namespace android
-
-#endif  // _ANDROID_MEDIA_DATASOURCECALLBACK_H_
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index cbc820b..05aaa82 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -745,6 +745,8 @@
 status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const {
     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;
 }
 
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/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index de60b08..e7487c3 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -23,9 +23,8 @@
 #include <media/MediaAnalyticsItem.h>
 
 
-// This source file is compiled and linked into both:
+// This source file is compiled and linked into:
 // core/jni/ (libandroid_runtime.so)
-// media/jni (libmedia2_jni.so)
 
 namespace android {
 
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
deleted file mode 100644
index 3069161..0000000
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ /dev/null
@@ -1,1477 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaPlayer2-JNI"
-#include "utils/Log.h"
-
-#include <sys/stat.h>
-
-#include <media/AudioResamplerPublic.h>
-#include <media/DataSourceDesc.h>
-#include <media/MediaHTTPService.h>
-#include <media/MediaAnalyticsItem.h>
-#include <media/NdkWrapper.h>
-#include <media/stagefright/Utils.h>
-#include <media/stagefright/foundation/ByteUtils.h>  // for FOURCC definition
-#include <mediaplayer2/JAudioTrack.h>
-#include <mediaplayer2/JavaVMHelper.h>
-#include <mediaplayer2/JMedia2HTTPService.h>
-#include <mediaplayer2/mediaplayer2.h>
-#include <stdio.h>
-#include <assert.h>
-#include <limits.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <utils/threads.h>
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "android/native_window_jni.h"
-#include "log/log.h"
-#include "utils/Errors.h"  // for status_t
-#include "utils/KeyedVector.h"
-#include "utils/String8.h"
-#include "android_media_BufferingParams.h"
-#include "android_media_DataSourceCallback.h"
-#include "android_media_MediaMetricsJNI.h"
-#include "android_media_PlaybackParams.h"
-#include "android_media_SyncParams.h"
-#include "android_media_VolumeShaper.h"
-
-#include "android_os_Parcel.h"
-#include "android_util_Binder.h"
-#include <binder/Parcel.h>
-
-#include "mediaplayer2.pb.h"
-
-using android::media::MediaPlayer2Proto::PlayerMessage;
-
-// Modular DRM begin
-#define FIND_CLASS(var, className) \
-var = env->FindClass(className); \
-LOG_FATAL_IF(! (var), "Unable to find class " className);
-
-#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
-var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
-LOG_FATAL_IF(! (var), "Unable to find method " fieldName);
-
-struct StateExceptionFields {
-    jmethodID init;
-    jclass classId;
-};
-
-static StateExceptionFields gStateExceptionFields;
-// Modular DRM end
-
-// ----------------------------------------------------------------------------
-
-using namespace android;
-
-using media::VolumeShaper;
-
-// ----------------------------------------------------------------------------
-
-struct fields_t {
-    jfieldID    context;               // passed from Java to native, used for creating JWakeLock
-    jfieldID    nativeContext;         // mNativeContext in MediaPlayer2.java
-    jfieldID    surface_texture;
-
-    jmethodID   post_event;
-
-    jmethodID   proxyConfigGetHost;
-    jmethodID   proxyConfigGetPort;
-    jmethodID   proxyConfigGetExclusionList;
-};
-static fields_t fields;
-
-static BufferingParams::fields_t gBufferingParamsFields;
-static PlaybackParams::fields_t gPlaybackParamsFields;
-static SyncParams::fields_t gSyncParamsFields;
-static VolumeShaperHelper::fields_t gVolumeShaperFields;
-
-static Mutex sLock;
-
-static bool ConvertKeyValueArraysToKeyedVector(
-        JNIEnv *env, jobjectArray keys, jobjectArray values,
-        KeyedVector<String8, String8>* keyedVector) {
-
-    int nKeyValuePairs = 0;
-    bool failed = false;
-    if (keys != NULL && values != NULL) {
-        nKeyValuePairs = env->GetArrayLength(keys);
-        failed = (nKeyValuePairs != env->GetArrayLength(values));
-    }
-
-    if (!failed) {
-        failed = ((keys != NULL && values == NULL) ||
-                  (keys == NULL && values != NULL));
-    }
-
-    if (failed) {
-        ALOGE("keys and values arrays have different length");
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return false;
-    }
-
-    for (int i = 0; i < nKeyValuePairs; ++i) {
-        // No need to check on the ArrayIndexOutOfBoundsException, since
-        // it won't happen here.
-        jstring key = (jstring) env->GetObjectArrayElement(keys, i);
-        jstring value = (jstring) env->GetObjectArrayElement(values, i);
-
-        const char* keyStr = env->GetStringUTFChars(key, NULL);
-        if (!keyStr) {  // OutOfMemoryError
-            return false;
-        }
-
-        const char* valueStr = env->GetStringUTFChars(value, NULL);
-        if (!valueStr) {  // OutOfMemoryError
-            env->ReleaseStringUTFChars(key, keyStr);
-            return false;
-        }
-
-        keyedVector->add(String8(keyStr), String8(valueStr));
-
-        env->ReleaseStringUTFChars(key, keyStr);
-        env->ReleaseStringUTFChars(value, valueStr);
-        env->DeleteLocalRef(key);
-        env->DeleteLocalRef(value);
-    }
-    return true;
-}
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNIMediaPlayer2Listener: public MediaPlayer2Listener
-{
-public:
-    JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz);
-    ~JNIMediaPlayer2Listener();
-    virtual void notify(int64_t srcId, int msg, int ext1, int ext2,
-                        const PlayerMessage *obj = NULL) override;
-private:
-    JNIMediaPlayer2Listener();
-    jclass      mClass;     // Reference to MediaPlayer2 class
-    jobject     mObject;    // Weak ref to MediaPlayer2 Java object to call on
-};
-
-JNIMediaPlayer2Listener::JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
-    // Hold onto the MediaPlayer2 class for use in calling the static method
-    // that posts events to the application thread.
-    jclass clazz = env->GetObjectClass(thiz);
-    if (clazz == NULL) {
-        ALOGE("Can't find android/media/MediaPlayer2");
-        jniThrowException(env, "java/lang/Exception", NULL);
-        return;
-    }
-    mClass = (jclass)env->NewGlobalRef(clazz);
-
-    // We use a weak reference so the MediaPlayer2 object can be garbage collected.
-    // The reference is only used as a proxy for callbacks.
-    mObject  = env->NewGlobalRef(weak_thiz);
-}
-
-JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener()
-{
-    // remove global references
-    JNIEnv *env = JavaVMHelper::getJNIEnv();
-    env->DeleteGlobalRef(mObject);
-    env->DeleteGlobalRef(mClass);
-}
-
-void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2,
-        const PlayerMessage* obj)
-{
-    JNIEnv *env = JavaVMHelper::getJNIEnv();
-    if (obj != NULL) {
-        int size = obj->ByteSize();
-        jbyte* temp = new jbyte[size];
-        obj->SerializeToArray(temp, size);
-
-        // return the response as a byte array.
-        jbyteArray out = env->NewByteArray(size);
-        env->SetByteArrayRegion(out, 0, size, temp);
-        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
-                srcId, msg, ext1, ext2, out);
-        delete[] temp;
-    } else {
-        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
-                srcId, msg, ext1, ext2, NULL);
-    }
-    if (env->ExceptionCheck()) {
-        ALOGW("An exception occurred while notifying an event.");
-        jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
-        env->ExceptionClear();
-    }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz)
-{
-    Mutex::Autolock l(sLock);
-    MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
-    return sp<MediaPlayer2>(p);
-}
-
-static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player)
-{
-    Mutex::Autolock l(sLock);
-    sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.nativeContext);
-    if (player.get()) {
-        player->incStrong((void*)setMediaPlayer);
-    }
-    if (old != 0) {
-        old->decStrong((void*)setMediaPlayer);
-    }
-    env->SetLongField(thiz, fields.nativeContext, (jlong)player.get());
-    return old;
-}
-
-// If exception is NULL and opStatus is not OK, this method sends an error
-// event to the client application; otherwise, if exception is not NULL and
-// opStatus is not OK, this method throws the given exception to the client
-// application.
-static void process_media_player_call(
-    JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
-{
-    if (exception == NULL) {  // Don't throw exception. Instead, send an event.
-        if (opStatus != (status_t) OK) {
-            sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-            if (mp != 0) {
-                int64_t srcId = 0;
-                mp->getSrcId(&srcId);
-                mp->notify(srcId, MEDIA2_ERROR, opStatus, 0);
-            }
-        }
-    } else {  // Throw exception!
-        if ( opStatus == (status_t) INVALID_OPERATION ) {
-            jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        } else if ( opStatus == (status_t) BAD_VALUE ) {
-            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        } else if ( opStatus == (status_t) PERMISSION_DENIED ) {
-            jniThrowException(env, "java/lang/SecurityException", NULL);
-        } else if ( opStatus != (status_t) OK ) {
-            if (strlen(message) > 230) {
-               // if the message is too long, don't bother displaying the status code
-               jniThrowException( env, exception, message);
-            } else {
-               char msg[256];
-                // append the status code to the message
-               sprintf(msg, "%s: status=0x%X", message, opStatus);
-               jniThrowException( env, exception, msg);
-            }
-        }
-    }
-}
-
-static void
-android_media_MediaPlayer2_handleDataSourceUrl(
-        JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId,
-        jobject httpServiceObj, jstring path, jobjectArray keys, jobjectArray values,
-        jlong startPos, jlong endPos) {
-
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    if (path == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
-
-    const char *tmp = env->GetStringUTFChars(path, NULL);
-    if (tmp == NULL) {  // Out of memory
-        return;
-    }
-    ALOGV("handleDataSourceUrl: path %s, srcId %lld, start %lld, end %lld",
-          tmp, (long long)srcId, (long long)startPos, (long long)endPos);
-
-    if (strncmp(tmp, "content://", 10) == 0) {
-        ALOGE("handleDataSourceUrl: content scheme is not supported in native code");
-        jniThrowException(env, "java/io/IOException",
-                          "content scheme is not supported in native code");
-        return;
-    }
-
-    sp<DataSourceDesc> dsd = new DataSourceDesc();
-    dsd->mId = srcId;
-    dsd->mType = DataSourceDesc::TYPE_URL;
-    dsd->mUrl = tmp;
-    dsd->mStartPositionMs = startPos;
-    dsd->mEndPositionMs = endPos;
-
-    env->ReleaseStringUTFChars(path, tmp);
-    tmp = NULL;
-
-    // We build a KeyedVector out of the key and val arrays
-    if (!ConvertKeyValueArraysToKeyedVector(
-            env, keys, values, &dsd->mHeaders)) {
-        return;
-    }
-
-    sp<MediaHTTPService> httpService;
-    if (httpServiceObj != NULL) {
-        httpService = new JMedia2HTTPService(env, httpServiceObj);
-    }
-    dsd->mHttpService = httpService;
-
-    status_t err;
-    if (isCurrent) {
-        err = mp->setDataSource(dsd);
-    } else {
-        err = mp->prepareNextDataSource(dsd);
-    }
-    process_media_player_call(env, thiz, err,
-            "java/io/IOException", "handleDataSourceUrl failed." );
-}
-
-static void
-android_media_MediaPlayer2_handleDataSourceFD(
-        JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId,
-        jobject fileDescriptor, jlong offset, jlong length,
-        jlong startPos, jlong endPos) {
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    if (fileDescriptor == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
-    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-    ALOGV("handleDataSourceFD: srcId=%lld, fd=%d (%s), offset=%lld, length=%lld, "
-          "start=%lld, end=%lld",
-          (long long)srcId, fd, nameForFd(fd).c_str(), (long long)offset, (long long)length,
-          (long long)startPos, (long long)endPos);
-
-    struct stat sb;
-    int ret = fstat(fd, &sb);
-    if (ret != 0) {
-        ALOGE("handleDataSourceFD: fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
-        jniThrowException(env, "java/io/IOException", "handleDataSourceFD failed fstat");
-        return;
-    }
-
-    ALOGV("st_dev  = %llu", static_cast<unsigned long long>(sb.st_dev));
-    ALOGV("st_mode = %u", sb.st_mode);
-    ALOGV("st_uid  = %lu", static_cast<unsigned long>(sb.st_uid));
-    ALOGV("st_gid  = %lu", static_cast<unsigned long>(sb.st_gid));
-    ALOGV("st_size = %llu", static_cast<unsigned long long>(sb.st_size));
-
-    if (offset >= sb.st_size) {
-        ALOGE("handleDataSourceFD: offset is out of range");
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                          "handleDataSourceFD failed, offset is out of range.");
-        return;
-    }
-    if (offset + length > sb.st_size) {
-        length = sb.st_size - offset;
-        ALOGV("handleDataSourceFD: adjusted length = %lld", (long long)length);
-    }
-
-    sp<DataSourceDesc> dsd = new DataSourceDesc();
-    dsd->mId = srcId;
-    dsd->mType = DataSourceDesc::TYPE_FD;
-    dsd->mFD = fd;
-    dsd->mFDOffset = offset;
-    dsd->mFDLength = length;
-    dsd->mStartPositionMs = startPos;
-    dsd->mEndPositionMs = endPos;
-
-    status_t err;
-    if (isCurrent) {
-        err = mp->setDataSource(dsd);
-    } else {
-        err = mp->prepareNextDataSource(dsd);
-    }
-    process_media_player_call(env, thiz, err,
-            "java/io/IOException", "handleDataSourceFD failed." );
-}
-
-static void
-android_media_MediaPlayer2_handleDataSourceCallback(
-    JNIEnv *env, jobject thiz, jboolean isCurrent, jlong srcId, jobject dataSource,
-    jlong startPos, jlong endPos)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    if (dataSource == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
-    sp<DataSource> callbackDataSource = new JDataSourceCallback(env, dataSource);
-    sp<DataSourceDesc> dsd = new DataSourceDesc();
-    dsd->mId = srcId;
-    dsd->mType = DataSourceDesc::TYPE_CALLBACK;
-    dsd->mCallbackSource = callbackDataSource;
-    dsd->mStartPositionMs = startPos;
-    dsd->mEndPositionMs = endPos;
-
-    status_t err;
-    if (isCurrent) {
-        err = mp->setDataSource(dsd);
-    } else {
-        err = mp->prepareNextDataSource(dsd);
-    }
-    process_media_player_call(env, thiz, err,
-            "java/lang/RuntimeException", "handleDataSourceCallback failed." );
-}
-
-static sp<ANativeWindowWrapper>
-getVideoSurfaceTexture(JNIEnv* env, jobject thiz) {
-    ANativeWindow * const p = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture);
-    return new ANativeWindowWrapper(p);
-}
-
-static void
-decVideoSurfaceRef(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return;
-    }
-
-    ANativeWindow * const old_anw = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture);
-    if (old_anw != NULL) {
-        ANativeWindow_release(old_anw);
-        env->SetLongField(thiz, fields.surface_texture, (jlong)NULL);
-    }
-}
-
-static void
-setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        if (mediaPlayerMustBeAlive) {
-            jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        }
-        return;
-    }
-
-    decVideoSurfaceRef(env, thiz);
-
-    ANativeWindow* anw = NULL;
-    if (jsurface) {
-        anw = ANativeWindow_fromSurface(env, jsurface);
-        if (anw == NULL) {
-            jniThrowException(env, "java/lang/IllegalArgumentException",
-                    "The surface has been released");
-            return;
-        }
-    }
-
-    env->SetLongField(thiz, fields.surface_texture, (jlong)anw);
-
-    // This will fail if the media player has not been initialized yet. This
-    // can be the case if setDisplay() on MediaPlayer2.java has been called
-    // before setDataSource(). The redundant call to setVideoSurfaceTexture()
-    // in prepare/prepare covers for this case.
-    mp->setVideoSurfaceTexture(new ANativeWindowWrapper(anw));
-}
-
-static void
-android_media_MediaPlayer2_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
-{
-    setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
-}
-
-static jobject
-android_media_MediaPlayer2_getBufferingParams(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    BufferingParams bp;
-    BufferingSettings &settings = bp.settings;
-    process_media_player_call(
-            env, thiz, mp->getBufferingSettings(&settings),
-            "java/lang/IllegalStateException", "unexpected error");
-    if (env->ExceptionCheck()) {
-        return nullptr;
-    }
-    ALOGV("getBufferingSettings:{%s}", settings.toString().string());
-
-    return bp.asJobject(env, gBufferingParamsFields);
-}
-
-static void
-android_media_MediaPlayer2_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
-{
-    if (params == NULL) {
-        return;
-    }
-
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    BufferingParams bp;
-    bp.fillFromJobject(env, gBufferingParamsFields, params);
-    ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
-
-    process_media_player_call(
-            env, thiz, mp->setBufferingSettings(bp.settings),
-            "java/lang/IllegalStateException", "unexpected error");
-}
-
-static void
-android_media_MediaPlayer2_playNextDataSource(JNIEnv *env, jobject thiz, jlong srcId)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    process_media_player_call(env, thiz, mp->playNextDataSource((int64_t)srcId),
-            "java/io/IOException", "playNextDataSource failed." );
-}
-
-static void
-android_media_MediaPlayer2_prepare(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    // Handle the case where the display surface was set before the mp was
-    // initialized. We try again to make it stick.
-    sp<ANativeWindowWrapper> st = getVideoSurfaceTexture(env, thiz);
-    mp->setVideoSurfaceTexture(st);
-
-    process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );
-}
-
-static void
-android_media_MediaPlayer2_start(JNIEnv *env, jobject thiz)
-{
-    ALOGV("start");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->start(), NULL, NULL );
-}
-
-static void
-android_media_MediaPlayer2_pause(JNIEnv *env, jobject thiz)
-{
-    ALOGV("pause");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->pause(), NULL, NULL );
-}
-
-static void
-android_media_MediaPlayer2_setPlaybackParams(JNIEnv *env, jobject thiz, jobject params)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    PlaybackParams pbp;
-    pbp.fillFromJobject(env, gPlaybackParamsFields, params);
-    ALOGV("setPlaybackParams: %d:%f %d:%f %d:%u %d:%u",
-            pbp.speedSet, pbp.audioRate.mSpeed,
-            pbp.pitchSet, pbp.audioRate.mPitch,
-            pbp.audioFallbackModeSet, pbp.audioRate.mFallbackMode,
-            pbp.audioStretchModeSet, pbp.audioRate.mStretchMode);
-
-    AudioPlaybackRate rate;
-    status_t err = mp->getPlaybackSettings(&rate);
-    if (err == OK) {
-        bool updatedRate = false;
-        if (pbp.speedSet) {
-            rate.mSpeed = pbp.audioRate.mSpeed;
-            updatedRate = true;
-        }
-        if (pbp.pitchSet) {
-            rate.mPitch = pbp.audioRate.mPitch;
-            updatedRate = true;
-        }
-        if (pbp.audioFallbackModeSet) {
-            rate.mFallbackMode = pbp.audioRate.mFallbackMode;
-            updatedRate = true;
-        }
-        if (pbp.audioStretchModeSet) {
-            rate.mStretchMode = pbp.audioRate.mStretchMode;
-            updatedRate = true;
-        }
-        if (updatedRate) {
-            err = mp->setPlaybackSettings(rate);
-        }
-    }
-    process_media_player_call(
-            env, thiz, err,
-            "java/lang/IllegalStateException", "unexpected error");
-}
-
-static jobject
-android_media_MediaPlayer2_getPlaybackParams(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    PlaybackParams pbp;
-    AudioPlaybackRate &audioRate = pbp.audioRate;
-    process_media_player_call(
-            env, thiz, mp->getPlaybackSettings(&audioRate),
-            "java/lang/IllegalStateException", "unexpected error");
-    if (env->ExceptionCheck()) {
-        return nullptr;
-    }
-    ALOGV("getPlaybackSettings: %f %f %d %d",
-            audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode);
-
-    pbp.speedSet = true;
-    pbp.pitchSet = true;
-    pbp.audioFallbackModeSet = true;
-    pbp.audioStretchModeSet = true;
-
-    return pbp.asJobject(env, gPlaybackParamsFields);
-}
-
-static void
-android_media_MediaPlayer2_setSyncParams(JNIEnv *env, jobject thiz, jobject params)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    SyncParams scp;
-    scp.fillFromJobject(env, gSyncParamsFields, params);
-    ALOGV("setSyncParams: %d:%d %d:%d %d:%f %d:%f",
-          scp.syncSourceSet, scp.sync.mSource,
-          scp.audioAdjustModeSet, scp.sync.mAudioAdjustMode,
-          scp.toleranceSet, scp.sync.mTolerance,
-          scp.frameRateSet, scp.frameRate);
-
-    AVSyncSettings avsync;
-    float videoFrameRate;
-    status_t err = mp->getSyncSettings(&avsync, &videoFrameRate);
-    if (err == OK) {
-        bool updatedSync = scp.frameRateSet;
-        if (scp.syncSourceSet) {
-            avsync.mSource = scp.sync.mSource;
-            updatedSync = true;
-        }
-        if (scp.audioAdjustModeSet) {
-            avsync.mAudioAdjustMode = scp.sync.mAudioAdjustMode;
-            updatedSync = true;
-        }
-        if (scp.toleranceSet) {
-            avsync.mTolerance = scp.sync.mTolerance;
-            updatedSync = true;
-        }
-        if (updatedSync) {
-            err = mp->setSyncSettings(avsync, scp.frameRateSet ? scp.frameRate : -1.f);
-        }
-    }
-    process_media_player_call(
-            env, thiz, err,
-            "java/lang/IllegalStateException", "unexpected error");
-}
-
-static jobject
-android_media_MediaPlayer2_getSyncParams(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    SyncParams scp;
-    scp.frameRate = -1.f;
-    process_media_player_call(
-            env, thiz, mp->getSyncSettings(&scp.sync, &scp.frameRate),
-            "java/lang/IllegalStateException", "unexpected error");
-    if (env->ExceptionCheck()) {
-        return nullptr;
-    }
-
-    ALOGV("getSyncSettings: %d %d %f %f",
-            scp.sync.mSource, scp.sync.mAudioAdjustMode, scp.sync.mTolerance, scp.frameRate);
-
-    // sanity check params
-    if (scp.sync.mSource >= AVSYNC_SOURCE_MAX
-            || scp.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX
-            || scp.sync.mTolerance < 0.f
-            || scp.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) {
-        jniThrowException(env,  "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    scp.syncSourceSet = true;
-    scp.audioAdjustModeSet = true;
-    scp.toleranceSet = true;
-    scp.frameRateSet = scp.frameRate >= 0.f;
-
-    return scp.asJobject(env, gSyncParamsFields);
-}
-
-static void
-android_media_MediaPlayer2_seekTo(JNIEnv *env, jobject thiz, jlong msec, jint mode)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    ALOGV("seekTo: %lld(msec), mode=%d", (long long)msec, mode);
-    process_media_player_call(env, thiz, mp->seekTo((int64_t)msec, (MediaPlayer2SeekMode)mode),
-                              NULL, NULL);
-}
-
-static jint
-android_media_MediaPlayer2_getState(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return MEDIAPLAYER2_STATE_IDLE;
-    }
-    return (jint)mp->getState();
-}
-
-static jobject
-android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    char *buffer = NULL;
-    size_t length = 0;
-    status_t status = mp->getMetrics(&buffer, &length);
-    if (status != OK) {
-        ALOGD("getMetrics() failed: %d", status);
-        return (jobject) NULL;
-    }
-
-    jobject mybundle = MediaMetricsJNI::writeAttributesToBundle(env, NULL, buffer, length);
-
-    free(buffer);
-
-    return mybundle;
-}
-
-static jlong
-android_media_MediaPlayer2_getCurrentPosition(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-    int64_t msec;
-    process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL );
-    ALOGV("getCurrentPosition: %lld (msec)", (long long)msec);
-    return (jlong) msec;
-}
-
-static jlong
-android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz, jlong srcId)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-    int64_t msec;
-    process_media_player_call( env, thiz, mp->getDuration(srcId, &msec), NULL, NULL );
-    ALOGV("getDuration: %lld (msec)", (long long)msec);
-    return (jlong) msec;
-}
-
-static void
-android_media_MediaPlayer2_reset(JNIEnv *env, jobject thiz)
-{
-    ALOGV("reset");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->reset(), NULL, NULL );
-}
-
-static jboolean
-android_media_MediaPlayer2_setAudioAttributes(JNIEnv *env, jobject thiz, jobject attributes)
-{
-    ALOGV("setAudioAttributes");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return false;
-    }
-    status_t err = mp->setAudioAttributes(attributes);
-    return err == OK;
-}
-
-static jobject
-android_media_MediaPlayer2_getAudioAttributes(JNIEnv *env, jobject thiz)
-{
-    ALOGV("getAudioAttributes");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    return mp->getAudioAttributes();
-}
-
-static void
-android_media_MediaPlayer2_setLooping(JNIEnv *env, jobject thiz, jboolean looping)
-{
-    ALOGV("setLooping: %d", looping);
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL );
-}
-
-static jboolean
-android_media_MediaPlayer2_isLooping(JNIEnv *env, jobject thiz)
-{
-    ALOGV("isLooping");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return JNI_FALSE;
-    }
-    return mp->isLooping() ? JNI_TRUE : JNI_FALSE;
-}
-
-static void
-android_media_MediaPlayer2_setVolume(JNIEnv *env, jobject thiz, jfloat volume)
-{
-    ALOGV("setVolume: volume %f", (float) volume);
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->setVolume((float) volume), NULL, NULL );
-}
-
-static jbyteArray
-android_media_MediaPlayer2_invoke(JNIEnv *env, jobject thiz, jbyteArray requestData) {
-    sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz);
-    if (media_player == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-
-    // Get the byte[] pointer and data length.
-    jbyte* pData = env->GetByteArrayElements(requestData, NULL);
-    jsize pDataLen = env->GetArrayLength(requestData);
-
-    // Deserialize from the byte stream.
-    PlayerMessage request;
-    PlayerMessage response;
-    request.ParseFromArray(pData, pDataLen);
-
-    process_media_player_call( env, thiz, media_player->invoke(request, &response),
-            "java.lang.RuntimeException", NULL );
-    if (env->ExceptionCheck()) {
-        return NULL;
-    }
-
-    int size = response.ByteSize();
-    jbyte* temp = new jbyte[size];
-    response.SerializeToArray(temp, size);
-
-    // return the response as a byte array.
-    jbyteArray out = env->NewByteArray(size);
-    env->SetByteArrayRegion(out, 0, size, temp);
-    delete[] temp;
-
-    return out;
-}
-
-// This function gets some field IDs, which in turn causes class initialization.
-// It is called from a static block in MediaPlayer2, which won't run until the
-// first time an instance of this class is used.
-static void
-android_media_MediaPlayer2_native_init(JNIEnv *env)
-{
-    jclass clazz;
-
-    clazz = env->FindClass("android/media/MediaPlayer2");
-    if (clazz == NULL) {
-        return;
-    }
-
-    fields.context = env->GetFieldID(clazz, "mContext", "Landroid/content/Context;");
-    if (fields.context == NULL) {
-        return;
-    }
-
-    fields.nativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
-    if (fields.nativeContext == NULL) {
-        return;
-    }
-
-    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
-                                               "(Ljava/lang/Object;JIII[B)V");
-    if (fields.post_event == NULL) {
-        return;
-    }
-
-    fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
-    if (fields.surface_texture == NULL) {
-        return;
-    }
-
-    env->DeleteLocalRef(clazz);
-
-    clazz = env->FindClass("android/net/ProxyInfo");
-    if (clazz == NULL) {
-        return;
-    }
-
-    fields.proxyConfigGetHost =
-        env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
-
-    fields.proxyConfigGetPort =
-        env->GetMethodID(clazz, "getPort", "()I");
-
-    fields.proxyConfigGetExclusionList =
-        env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
-
-    env->DeleteLocalRef(clazz);
-
-    gBufferingParamsFields.init(env);
-
-    // Modular DRM
-    FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
-    if (clazz) {
-        GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
-        gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
-
-        env->DeleteLocalRef(clazz);
-    } else {
-        ALOGE("JNI android_media_MediaPlayer2_native_init couldn't "
-              "get clazz android/media/MediaDrm$MediaDrmStateException");
-    }
-
-    gPlaybackParamsFields.init(env);
-    gSyncParamsFields.init(env);
-    gVolumeShaperFields.init(env);
-}
-
-static void
-android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz,
-        jint sessionId, jobject weak_this)
-{
-    ALOGV("native_setup");
-    jobject context = env->GetObjectField(thiz, fields.context);
-    sp<MediaPlayer2> mp = MediaPlayer2::Create(sessionId, context);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
-        return;
-    }
-
-    // create new listener and give it to MediaPlayer2
-    sp<JNIMediaPlayer2Listener> listener = new JNIMediaPlayer2Listener(env, thiz, weak_this);
-    mp->setListener(listener);
-
-    // Stow our new C++ MediaPlayer2 in an opaque field in the Java object.
-    setMediaPlayer(env, thiz, mp);
-}
-
-static void
-android_media_MediaPlayer2_release(JNIEnv *env, jobject thiz)
-{
-    ALOGV("release");
-    decVideoSurfaceRef(env, thiz);
-    sp<MediaPlayer2> mp = setMediaPlayer(env, thiz, 0);
-    if (mp != NULL) {
-        // this prevents native callbacks after the object is released
-        mp->setListener(0);
-        mp->disconnect();
-    }
-}
-
-static void
-android_media_MediaPlayer2_native_finalize(JNIEnv *env, jobject thiz)
-{
-    ALOGV("native_finalize");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp != NULL) {
-        ALOGW("MediaPlayer2 finalized without being released");
-    }
-    android_media_MediaPlayer2_release(env, thiz);
-}
-
-static void android_media_MediaPlayer2_setAudioSessionId(JNIEnv *env,  jobject thiz,
-        jint sessionId) {
-    ALOGV("setAudioSessionId(): %d", sessionId);
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->setAudioSessionId((audio_session_t) sessionId), NULL,
-            NULL);
-}
-
-static jint android_media_MediaPlayer2_getAudioSessionId(JNIEnv *env,  jobject thiz) {
-    ALOGV("getAudioSessionId()");
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return 0;
-    }
-
-    return (jint) mp->getAudioSessionId();
-}
-
-static void
-android_media_MediaPlayer2_setAuxEffectSendLevel(JNIEnv *env, jobject thiz, jfloat level)
-{
-    ALOGV("setAuxEffectSendLevel: level %f", level);
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->setAuxEffectSendLevel(level), NULL, NULL );
-}
-
-static void android_media_MediaPlayer2_attachAuxEffect(JNIEnv *env,  jobject thiz, jint effectId) {
-    ALOGV("attachAuxEffect(): %d", effectId);
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-    process_media_player_call( env, thiz, mp->attachAuxEffect(effectId), NULL, NULL );
-}
-
-/////////////////////////////////////////////////////////////////////////////////////
-// Modular DRM begin
-
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err)
-{
-    ALOGE("Illegal DRM state exception: %s (%d)", msg, err);
-
-    jobject exception = env->NewObject(gStateExceptionFields.classId,
-            gStateExceptionFields.init, static_cast<int>(err),
-            env->NewStringUTF(msg));
-    env->Throw(static_cast<jthrowable>(exception));
-}
-
-// TODO: investigate if these can be shared with their MediaDrm counterparts
-static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char *msg = NULL)
-{
-    const char *drmMessage = "Unknown DRM Msg";
-
-    switch (err) {
-    case ERROR_DRM_UNKNOWN:
-        drmMessage = "General DRM error";
-        break;
-    case ERROR_DRM_NO_LICENSE:
-        drmMessage = "No license";
-        break;
-    case ERROR_DRM_LICENSE_EXPIRED:
-        drmMessage = "License expired";
-        break;
-    case ERROR_DRM_SESSION_NOT_OPENED:
-        drmMessage = "Session not opened";
-        break;
-    case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
-        drmMessage = "Not initialized";
-        break;
-    case ERROR_DRM_DECRYPT:
-        drmMessage = "Decrypt error";
-        break;
-    case ERROR_DRM_CANNOT_HANDLE:
-        drmMessage = "Unsupported scheme or data format";
-        break;
-    case ERROR_DRM_TAMPER_DETECTED:
-        drmMessage = "Invalid state";
-        break;
-    default:
-        break;
-    }
-
-    String8 vendorMessage;
-    if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
-        vendorMessage = String8::format("DRM vendor-defined error: %d", err);
-        drmMessage = vendorMessage.string();
-    }
-
-    if (err == BAD_VALUE) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", msg);
-        return true;
-    } else if (err == ERROR_DRM_NOT_PROVISIONED) {
-        jniThrowException(env, "android/media/NotProvisionedException", msg);
-        return true;
-    } else if (err == ERROR_DRM_RESOURCE_BUSY) {
-        jniThrowException(env, "android/media/ResourceBusyException", msg);
-        return true;
-    } else if (err == ERROR_DRM_DEVICE_REVOKED) {
-        jniThrowException(env, "android/media/DeniedByServerException", msg);
-        return true;
-    } else if (err == DEAD_OBJECT) {
-        jniThrowException(env, "android/media/MediaDrmResetException",
-                          "mediaserver died");
-        return true;
-    } else if (err != OK) {
-        String8 errbuf;
-        if (drmMessage != NULL) {
-            if (msg == NULL) {
-                msg = drmMessage;
-            } else {
-                errbuf = String8::format("%s: %s", msg, drmMessage);
-                msg = errbuf.string();
-            }
-        }
-        throwDrmStateException(env, msg, err);
-        return true;
-    }
-    return false;
-}
-
-static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray)
-{
-    Vector<uint8_t> vector;
-    size_t length = env->GetArrayLength(byteArray);
-    vector.insertAt((size_t)0, length);
-    env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
-    return vector;
-}
-
-static void android_media_MediaPlayer2_prepareDrm(JNIEnv *env, jobject thiz,
-                    jlong srcId, jbyteArray uuidObj, jbyteArray drmSessionIdObj)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    if (uuidObj == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
-    }
-
-    Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
-
-    if (uuid.size() != 16) {
-        jniThrowException(
-                          env,
-                          "java/lang/IllegalArgumentException",
-                          "invalid UUID size, expected 16 bytes");
-        return;
-    }
-
-    Vector<uint8_t> drmSessionId = JByteArrayToVector(env, drmSessionIdObj);
-
-    if (drmSessionId.size() == 0) {
-        jniThrowException(
-                          env,
-                          "java/lang/IllegalArgumentException",
-                          "empty drmSessionId");
-        return;
-    }
-
-    status_t err = mp->prepareDrm(srcId, uuid.array(), drmSessionId);
-    if (err != OK) {
-        if (err == INVALID_OPERATION) {
-            jniThrowException(
-                              env,
-                              "java/lang/IllegalStateException",
-                              "The player must be in prepared state.");
-        } else if (err == ERROR_DRM_CANNOT_HANDLE) {
-            jniThrowException(
-                              env,
-                              "android/media/UnsupportedSchemeException",
-                              "Failed to instantiate drm object.");
-        } else {
-            throwDrmExceptionAsNecessary(env, err, "Failed to prepare DRM scheme");
-        }
-    }
-}
-
-static void android_media_MediaPlayer2_releaseDrm(JNIEnv *env, jobject thiz, jlong srcId)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL ) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return;
-    }
-
-    status_t err = mp->releaseDrm(srcId);
-    if (err != OK) {
-        if (err == INVALID_OPERATION) {
-            jniThrowException(
-                              env,
-                              "java/lang/IllegalStateException",
-                              "Can not release DRM in an active player state.");
-        }
-    }
-}
-// Modular DRM end
-// ----------------------------------------------------------------------------
-
-/////////////////////////////////////////////////////////////////////////////////////
-// AudioRouting begin
-static jboolean android_media_MediaPlayer2_setPreferredDevice(JNIEnv *env, jobject thiz, jobject device)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return false;
-    }
-    return mp->setPreferredDevice(device) == NO_ERROR;
-}
-
-static jobject android_media_MediaPlayer2_getRoutedDevice(JNIEnv *env, jobject thiz)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return nullptr;
-    }
-    return mp->getRoutedDevice();
-}
-
-static void android_media_MediaPlayer2_addDeviceCallback(
-        JNIEnv* env, jobject thiz, jobject routingDelegate)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return;
-    }
-
-    status_t status = mp->addAudioDeviceCallback(routingDelegate);
-    if (status != NO_ERROR) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        ALOGE("enable device callback failed: %d", status);
-    }
-}
-
-static void android_media_MediaPlayer2_removeDeviceCallback(
-        JNIEnv* env, jobject thiz, jobject listener)
-{
-    sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
-    if (mp == NULL) {
-        return;
-    }
-
-    status_t status = mp->removeAudioDeviceCallback(listener);
-    if (status != NO_ERROR) {
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        ALOGE("enable device callback failed: %d", status);
-    }
-}
-
-// AudioRouting end
-// ----------------------------------------------------------------------------
-
-/////////////////////////////////////////////////////////////////////////////////////
-// AudioTrack.StreamEventCallback begin
-static void android_media_MediaPlayer2_native_on_tear_down(JNIEnv *env __unused,
-        jobject thiz __unused, jlong callbackPtr, jlong userDataPtr)
-{
-    JAudioTrack::callback_t callback = (JAudioTrack::callback_t) callbackPtr;
-    if (callback != NULL) {
-        callback(JAudioTrack::EVENT_NEW_IAUDIOTRACK, (void *) userDataPtr, NULL);
-    }
-}
-
-static void android_media_MediaPlayer2_native_on_stream_presentation_end(JNIEnv *env __unused,
-        jobject thiz __unused, jlong callbackPtr, jlong userDataPtr)
-{
-    JAudioTrack::callback_t callback = (JAudioTrack::callback_t) callbackPtr;
-    if (callback != NULL) {
-        callback(JAudioTrack::EVENT_STREAM_END, (void *) userDataPtr, NULL);
-    }
-}
-
-static void android_media_MediaPlayer2_native_on_stream_data_request(JNIEnv *env __unused,
-        jobject thiz __unused, jlong jAudioTrackPtr, jlong callbackPtr, jlong userDataPtr)
-{
-    JAudioTrack::callback_t callback = (JAudioTrack::callback_t) callbackPtr;
-    JAudioTrack* track = (JAudioTrack *) jAudioTrackPtr;
-    if (callback != NULL && track != NULL) {
-        JAudioTrack::Buffer* buffer = new JAudioTrack::Buffer();
-
-        size_t bufferSizeInFrames = track->frameCount();
-        audio_format_t format = track->format();
-
-        size_t bufferSizeInBytes;
-        if (audio_has_proportional_frames(format)) {
-            bufferSizeInBytes =
-                    bufferSizeInFrames * audio_bytes_per_sample(format) * track->channelCount();
-        } else {
-            // See Javadoc of AudioTrack::getBufferSizeInFrames().
-            bufferSizeInBytes = bufferSizeInFrames;
-        }
-
-        uint8_t* byteBuffer = new uint8_t[bufferSizeInBytes];
-        buffer->mSize = bufferSizeInBytes;
-        buffer->mData = (void *) byteBuffer;
-
-        callback(JAudioTrack::EVENT_MORE_DATA, (void *) userDataPtr, buffer);
-
-        if (buffer->mSize > 0 && buffer->mData == byteBuffer) {
-            track->write(buffer->mData, buffer->mSize, true /* Blocking */);
-        }
-
-        delete[] byteBuffer;
-        delete buffer;
-    }
-}
-
-
-// AudioTrack.StreamEventCallback end
-// ----------------------------------------------------------------------------
-
-static const JNINativeMethod gMethods[] = {
-    {
-        "nativeHandleDataSourceUrl",
-        "(ZJLandroid/media/Media2HTTPService;Ljava/lang/String;[Ljava/lang/String;"
-        "[Ljava/lang/String;JJ)V",
-        (void *)android_media_MediaPlayer2_handleDataSourceUrl
-    },
-    {
-        "nativeHandleDataSourceFD",
-        "(ZJLjava/io/FileDescriptor;JJJJ)V",
-        (void *)android_media_MediaPlayer2_handleDataSourceFD
-    },
-    {
-        "nativeHandleDataSourceCallback",
-        "(ZJLandroid/media/DataSourceCallback;JJ)V",
-        (void *)android_media_MediaPlayer2_handleDataSourceCallback
-    },
-    {"nativePlayNextDataSource", "(J)V",                        (void *)android_media_MediaPlayer2_playNextDataSource},
-    {"native_setVideoSurface", "(Landroid/view/Surface;)V",     (void *)android_media_MediaPlayer2_setVideoSurface},
-    {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams},
-    {"native_setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
-    {"native_prepare",      "()V",                              (void *)android_media_MediaPlayer2_prepare},
-    {"native_start",        "()V",                              (void *)android_media_MediaPlayer2_start},
-    {"native_getState",     "()I",                              (void *)android_media_MediaPlayer2_getState},
-    {"native_getMetrics",   "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics},
-    {"native_setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
-    {"getPlaybackParams", "()Landroid/media/PlaybackParams;",   (void *)android_media_MediaPlayer2_getPlaybackParams},
-    {"native_setSyncParams",     "(Landroid/media/SyncParams;)V",     (void *)android_media_MediaPlayer2_setSyncParams},
-    {"getSyncParams",     "()Landroid/media/SyncParams;",       (void *)android_media_MediaPlayer2_getSyncParams},
-    {"native_seekTo",       "(JI)V",                            (void *)android_media_MediaPlayer2_seekTo},
-    {"native_pause",        "()V",                              (void *)android_media_MediaPlayer2_pause},
-    {"getCurrentPosition",  "()J",                              (void *)android_media_MediaPlayer2_getCurrentPosition},
-    {"native_getDuration",  "(J)J",                             (void *)android_media_MediaPlayer2_getDuration},
-    {"native_release",      "()V",                              (void *)android_media_MediaPlayer2_release},
-    {"native_reset",        "()V",                              (void *)android_media_MediaPlayer2_reset},
-    {"native_setAudioAttributes", "(Landroid/media/AudioAttributes;)Z", (void *)android_media_MediaPlayer2_setAudioAttributes},
-    {"native_getAudioAttributes", "()Landroid/media/AudioAttributes;", (void *)android_media_MediaPlayer2_getAudioAttributes},
-    {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer2_setLooping},
-    {"isLooping",           "()Z",                              (void *)android_media_MediaPlayer2_isLooping},
-    {"native_setVolume",    "(F)V",                             (void *)android_media_MediaPlayer2_setVolume},
-    {"native_invoke",       "([B)[B",                           (void *)android_media_MediaPlayer2_invoke},
-    {"native_init",         "()V",                              (void *)android_media_MediaPlayer2_native_init},
-    {"native_setup",        "(ILjava/lang/Object;)V",           (void *)android_media_MediaPlayer2_native_setup},
-    {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer2_native_finalize},
-    {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer2_getAudioSessionId},
-    {"native_setAudioSessionId", "(I)V",                        (void *)android_media_MediaPlayer2_setAudioSessionId},
-    {"native_setAuxEffectSendLevel", "(F)V",                    (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
-    {"native_attachAuxEffect", "(I)V",                          (void *)android_media_MediaPlayer2_attachAuxEffect},
-    // Modular DRM
-    { "native_prepareDrm", "(J[B[B)V",                          (void *)android_media_MediaPlayer2_prepareDrm },
-    { "native_releaseDrm", "(J)V",                              (void *)android_media_MediaPlayer2_releaseDrm },
-
-    // AudioRouting
-    {"native_setPreferredDevice", "(Landroid/media/AudioDeviceInfo;)Z", (void *)android_media_MediaPlayer2_setPreferredDevice},
-    {"getRoutedDevice", "()Landroid/media/AudioDeviceInfo;", (void *)android_media_MediaPlayer2_getRoutedDevice},
-    {"native_addDeviceCallback", "(Landroid/media/RoutingDelegate;)V", (void *)android_media_MediaPlayer2_addDeviceCallback},
-    {"native_removeDeviceCallback", "(Landroid/media/AudioRouting$OnRoutingChangedListener;)V",
-            (void *)android_media_MediaPlayer2_removeDeviceCallback},
-
-    // StreamEventCallback for JAudioTrack
-    {"native_stream_event_onTearDown",                "(JJ)V",  (void *)android_media_MediaPlayer2_native_on_tear_down},
-    {"native_stream_event_onStreamPresentationEnd",   "(JJ)V",  (void *)android_media_MediaPlayer2_native_on_stream_presentation_end},
-    {"native_stream_event_onStreamDataRequest",       "(JJJ)V", (void *)android_media_MediaPlayer2_native_on_stream_data_request},
-};
-
-// This function only registers the native methods
-static int register_android_media_MediaPlayer2(JNIEnv *env)
-{
-    return jniRegisterNativeMethods(env, "android/media/MediaPlayer2", gMethods, NELEM(gMethods));
-}
-
-jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
-{
-    JNIEnv* env = NULL;
-    jint result = -1;
-
-    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
-        ALOGE("ERROR: GetEnv failed\n");
-        goto bail;
-    }
-    assert(env != NULL);
-
-    if (register_android_media_MediaPlayer2(env) < 0) {
-        ALOGE("ERROR: MediaPlayer2 native registration failed\n");
-        goto bail;
-    }
-
-    JavaVMHelper::setJavaVM(vm);
-
-    /* success -- return valid version number */
-    result = JNI_VERSION_1_4;
-
-bail:
-    return result;
-}
-
-// KTHXBYE
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 85a007f..6b03e4d 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -16,9 +16,8 @@
 
 java_sdk_library {
     name: "com.android.mediadrm.signer",
-    srcs: [
-        "java/**/*.java",
-        ":framework-srcs",
-    ],
+    srcs: ["java/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
     api_packages: ["com.android.mediadrm.signer"],
 }
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/proto/Android.bp b/media/proto/Android.bp
deleted file mode 100644
index 2dc0d57..0000000
--- a/media/proto/Android.bp
+++ /dev/null
@@ -1,20 +0,0 @@
-java_library_static {
-    name: "mediaplayer2-protos",
-    host_supported: true,
-    proto: {
-        type: "lite",
-    },
-    srcs: ["mediaplayer2.proto"],
-    jarjar_rules: "jarjar-rules.txt",
-    sdk_version: "28",
-}
-
-cc_library_static {
-    name: "libmediaplayer2-protos",
-    host_supported: true,
-    proto: {
-        export_proto_headers: true,
-        type: "lite",
-    },
-    srcs: ["mediaplayer2.proto"],
-}
diff --git a/media/proto/jarjar-rules.txt b/media/proto/jarjar-rules.txt
deleted file mode 100644
index e73f86d..0000000
--- a/media/proto/jarjar-rules.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-rule com.google.protobuf.** android.media.protobuf.@1
-
diff --git a/media/proto/mediaplayer2.proto b/media/proto/mediaplayer2.proto
deleted file mode 100644
index 6287d6c..0000000
--- a/media/proto/mediaplayer2.proto
+++ /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.
- */
-
-syntax = "proto2";
-
-option optimize_for = LITE_RUNTIME;
-
-// C++ namespace: android::media:MediaPlayer2Proto:
-package android.media.MediaPlayer2Proto;
-
-option java_package = "android.media";
-option java_outer_classname = "MediaPlayer2Proto";
-
-message Value {
-    // The kind of value.
-    oneof kind {
-        // Represents a boolean value.
-        bool bool_value = 1;
-        // Represents an int32 value.
-        int32 int32_value = 2;
-        // Represents an uint32 value.
-        uint32 uint32_value = 3;
-        // Represents an int64 value.
-        int64 int64_value = 4;
-        // Represents an uint64 value.
-        uint64 uint64_value = 5;
-        // Represents a float value.
-        double float_value = 6;
-        // Represents a double value.
-        double double_value = 7;
-        // Represents a string value.
-        string string_value = 8;
-        // Represents a bytes value.
-        bytes bytes_value = 9;
-    }
-}
-
-message PlayerMessage {
-    repeated Value values = 1;
-}
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/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 9bcd677..342d796 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -18,6 +18,7 @@
     name: "BackupEncryption",
     srcs: ["src/**/*.java"],
     libs: ["backup-encryption-protos"],
+    static_libs: ["backuplib"],
     optimize: { enabled: false },
     platform_apis: true,
     certificate: "platform",
diff --git a/packages/BackupEncryption/AndroidManifest.xml b/packages/BackupEncryption/AndroidManifest.xml
index a705df5..4d174e3 100644
--- a/packages/BackupEncryption/AndroidManifest.xml
+++ b/packages/BackupEncryption/AndroidManifest.xml
@@ -20,5 +20,14 @@
     package="com.android.server.backup.encryption"
     android:sharedUserId="android.uid.system" >
 
-    <application android:allowBackup="false" />
+    <application android:allowBackup="false" >
+        <!-- This service does not need to be exported because it shares uid with the system server
+        which is the only client. -->
+        <service android:name=".BackupEncryptionService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.encryption.BACKUP_ENCRYPTION" />
+            </intent-filter>
+        </service>
+    </application>
 </manifest>
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/src/com/android/server/backup/encryption/BackupEncryptionService.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java
new file mode 100644
index 0000000..84fb0e6
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java
@@ -0,0 +1,63 @@
+/*
+ * 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 android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport;
+import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager;
+
+/**
+ * This service provides encryption of backup data. For an intent used to bind to this service, it
+ * provides an {@link IntermediateEncryptingTransport} which is an implementation of {@link
+ * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from the
+ * real {@link IBackupTransport}.
+ */
+public class BackupEncryptionService extends Service {
+    public static final String TAG = "BackupEncryption";
+    private static IntermediateEncryptingTransportManager sTransportManager = null;
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreate:" + this);
+        if (sTransportManager == null) {
+            Log.i(TAG, "Creating IntermediateEncryptingTransportManager");
+            sTransportManager = new IntermediateEncryptingTransportManager(this);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "onDestroy:" + this);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO (b141536117): Check connection with TransportClient.connect and return null on fail.
+        return sTransportManager.get(intent);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        sTransportManager.cleanup(intent);
+        return false;
+    }
+}
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/Chunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java
deleted file mode 100644
index ba32860..0000000
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/Chunk.java
+++ /dev/null
@@ -1,70 +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 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;
-    }
-}
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
index a448901..51d7d53 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
@@ -17,51 +17,41 @@
 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 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
+ * 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();
+
+    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;
 
-        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);
-            }
+        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 final Map<ChunkHash, Entry> mChunksByHash;
-
     private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) {
-        mChunksByHash = Collections.unmodifiableMap(new HashMap<>(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. */
@@ -81,19 +71,14 @@
         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;
+            mStart = start;
         }
 
         /** Returns the length of the chunk in bytes. */
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
index 8cb028e..9cda339 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
@@ -16,9 +16,9 @@
 
 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 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;
 
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/InlineLengthsEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
index 7b38dd4..6b9be9f 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
@@ -17,7 +17,7 @@
 package com.android.server.backup.encryption.chunking;
 
 import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
 
 import java.io.IOException;
 
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
index 567f75d..e707350 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
@@ -17,7 +17,7 @@
 package com.android.server.backup.encryption.chunking;
 
 import com.android.server.backup.encryption.chunk.ChunkOrderingType;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
 
 import java.io.IOException;
 
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.java
new file mode 100644
index 0000000..d7f7dc7
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.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 com.android.server.backup.encryption.client;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.util.Map;
+
+/**
+ * Contains methods for communicating with the parts of the backup server relevant to encryption.
+ */
+public interface CryptoBackupServer {
+    /**
+     * Uploads an incremental backup to the server.
+     *
+     * <p>Handles setting up and tearing down the connection.
+     *
+     * @param packageName the package to associate the data with
+     * @param oldDocId the id of the previous backup doc in Drive
+     * @param diffScript containing the actual backup data
+     * @param tertiaryKey the wrapped key used to encrypt this backup
+     * @return the id of the new backup doc in Drive.
+     */
+    String uploadIncrementalBackup(
+            String packageName,
+            String oldDocId,
+            byte[] diffScript,
+            WrappedKeyProto.WrappedKey tertiaryKey);
+
+    /**
+     * Uploads non-incremental backup to the server.
+     *
+     * <p>Handles setting up and tearing down the connection.
+     *
+     * @param packageName the package to associate the data with
+     * @param data the actual backup data
+     * @param tertiaryKey the wrapped key used to encrypt this backup
+     * @return the id of the new backup doc in Drive.
+     */
+    String uploadNonIncrementalBackup(
+            String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey);
+
+    /**
+     * Sets the alias of the active secondary key. This is the alias used to refer to the key in the
+     * {@link java.security.KeyStore}. It is also used to key storage for tertiary keys on the
+     * backup server. Also has to upload all existing tertiary keys, wrapped with the new key.
+     *
+     * @param keyAlias The ID of the secondary key.
+     * @param tertiaryKeys The tertiary keys, wrapped with the new secondary key.
+     */
+    void setActiveSecondaryKeyAlias(
+            String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys);
+}
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/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
index ec90f6c..1a281e7 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -16,6 +16,8 @@
 
 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;
@@ -46,15 +48,27 @@
      */
     public static TertiaryKeyRotationTracker getInstance(Context context) {
         return new TertiaryKeyRotationTracker(
-                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE));
+                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 mSharedPreferences}. */
+    /**
+     * New instance, storing data in {@code sharedPreferences} and initializing backup countdown to
+     * {@code maxBackupsTillRotation}.
+     */
     @VisibleForTesting
-    TertiaryKeyRotationTracker(SharedPreferences sharedPreferences) {
+    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;
     }
 
     /**
@@ -63,7 +77,7 @@
      * @param packageName The package name of the app.
      */
     public boolean isKeyRotationDue(String packageName) {
-        return getBackupsSinceRotation(packageName) >= MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION;
+        return getBackupsSinceRotation(packageName) >= mMaxBackupsTillRotation;
     }
 
     /**
@@ -84,7 +98,7 @@
                             packageName,
                             Math.max(
                                     0,
-                                    MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION
+                                    mMaxBackupsTillRotation
                                             - backupsSinceRotation)));
         }
     }
@@ -102,7 +116,7 @@
     public void markAllForRotation() {
         SharedPreferences.Editor editor = mSharedPreferences.edit();
         for (String packageName : mSharedPreferences.getAll().keySet()) {
-            editor.putInt(packageName, MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION);
+            editor.putInt(packageName, mMaxBackupsTillRotation);
         }
         editor.apply();
     }
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/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
index e3df3c1..f67f100 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
@@ -19,6 +19,7 @@
 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
@@ -30,7 +31,7 @@
      *
      * @return {@code this}, to allow use with try-with-resources
      */
-    DecryptedChunkOutput open() throws IOException;
+    DecryptedChunkOutput open() throws IOException, NoSuchAlgorithmException;
 
     /**
      * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also
@@ -43,12 +44,13 @@
      *     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;
+    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();
+    byte[] getDigest() throws NoSuchAlgorithmException;
 }
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java
new file mode 100644
index 0000000..ef13f23
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java
@@ -0,0 +1,243 @@
+/*
+ * 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.annotation.Nullable;
+import android.annotation.TargetApi;
+import android.os.Build.VERSION_CODES;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.BackupFileBuilder;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * Task which reads encrypted chunks from a {@link BackupEncrypter}, builds a backup file and
+ * uploads it to the server.
+ */
+@TargetApi(VERSION_CODES.P)
+public class EncryptedBackupTask {
+    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 int BITS_PER_BYTE = 8;
+
+    private static final String TAG = "EncryptedBackupTask";
+
+    private final CryptoBackupServer mCryptoBackupServer;
+    private final SecureRandom mSecureRandom;
+    private final String mPackageName;
+    private final ByteArrayOutputStream mBackupDataOutput;
+    private final BackupEncrypter mBackupEncrypter;
+    private final AtomicBoolean mCancelled;
+
+    /** Creates a new instance which reads data from the given input stream. */
+    public EncryptedBackupTask(
+            CryptoBackupServer cryptoBackupServer,
+            SecureRandom secureRandom,
+            String packageName,
+            BackupEncrypter backupEncrypter) {
+        mCryptoBackupServer = cryptoBackupServer;
+        mSecureRandom = secureRandom;
+        mPackageName = packageName;
+        mBackupEncrypter = backupEncrypter;
+
+        mBackupDataOutput = new ByteArrayOutputStream();
+        mCancelled = new AtomicBoolean(false);
+    }
+
+    /**
+     * Creates a non-incremental backup file and uploads it to the server.
+     *
+     * @param fingerprintMixerSalt Fingerprint mixer salt used for content-defined chunking during a
+     *     full backup. May be {@code null} for a key-value backup.
+     */
+    public ChunksMetadataProto.ChunkListing performNonIncrementalBackup(
+            SecretKey tertiaryKey,
+            WrappedKeyProto.WrappedKey wrappedTertiaryKey,
+            @Nullable byte[] fingerprintMixerSalt)
+            throws IOException, GeneralSecurityException {
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                performBackup(
+                        tertiaryKey,
+                        fingerprintMixerSalt,
+                        BackupFileBuilder.createForNonIncremental(mBackupDataOutput),
+                        new HashSet<>());
+
+        throwIfCancelled();
+
+        newChunkListing.documentId =
+                mCryptoBackupServer.uploadNonIncrementalBackup(
+                        mPackageName, mBackupDataOutput.toByteArray(), wrappedTertiaryKey);
+
+        return newChunkListing;
+    }
+
+    /** Creates an incremental backup file and uploads it to the server. */
+    public ChunksMetadataProto.ChunkListing performIncrementalBackup(
+            SecretKey tertiaryKey,
+            WrappedKeyProto.WrappedKey wrappedTertiaryKey,
+            ChunksMetadataProto.ChunkListing oldChunkListing)
+            throws IOException, GeneralSecurityException {
+
+        ChunksMetadataProto.ChunkListing newChunkListing =
+                performBackup(
+                        tertiaryKey,
+                        oldChunkListing.fingerprintMixerSalt,
+                        BackupFileBuilder.createForIncremental(mBackupDataOutput, oldChunkListing),
+                        getChunkHashes(oldChunkListing));
+
+        throwIfCancelled();
+
+        String oldDocumentId = oldChunkListing.documentId;
+        Slog.v(TAG, "Old doc id: " + oldDocumentId);
+
+        newChunkListing.documentId =
+                mCryptoBackupServer.uploadIncrementalBackup(
+                        mPackageName,
+                        oldDocumentId,
+                        mBackupDataOutput.toByteArray(),
+                        wrappedTertiaryKey);
+        return newChunkListing;
+    }
+
+    /**
+     * Signals to the task that the backup has been cancelled. If the upload has not yet started
+     * then the task will not upload any data to the server or save the new chunk listing.
+     */
+    public void cancel() {
+        mCancelled.getAndSet(true);
+    }
+
+    private void throwIfCancelled() {
+        if (mCancelled.get()) {
+            throw new CancellationException("EncryptedBackupTask was cancelled");
+        }
+    }
+
+    private ChunksMetadataProto.ChunkListing performBackup(
+            SecretKey tertiaryKey,
+            @Nullable byte[] fingerprintMixerSalt,
+            BackupFileBuilder backupFileBuilder,
+            Set<ChunkHash> existingChunkHashes)
+            throws IOException, GeneralSecurityException {
+        BackupEncrypter.Result result =
+                mBackupEncrypter.backup(tertiaryKey, fingerprintMixerSalt, existingChunkHashes);
+        backupFileBuilder.writeChunks(result.getAllChunks(), buildChunkMap(result.getNewChunks()));
+
+        ChunksMetadataProto.ChunkOrdering chunkOrdering =
+                backupFileBuilder.getNewChunkOrdering(result.getDigest());
+        backupFileBuilder.finish(buildMetadata(tertiaryKey, chunkOrdering));
+
+        return backupFileBuilder.getNewChunkListing(fingerprintMixerSalt);
+    }
+
+    /** Returns a set containing the hashes of every chunk in the given listing. */
+    private static Set<ChunkHash> getChunkHashes(ChunksMetadataProto.ChunkListing chunkListing) {
+        Set<ChunkHash> hashes = new HashSet<>();
+        for (ChunksMetadataProto.Chunk chunk : chunkListing.chunks) {
+            hashes.add(new ChunkHash(chunk.hash));
+        }
+        return hashes;
+    }
+
+    /** Returns a map from chunk hash to chunk containing every chunk in the given list. */
+    private static Map<ChunkHash, EncryptedChunk> buildChunkMap(List<EncryptedChunk> chunks) {
+        Map<ChunkHash, EncryptedChunk> chunkMap = new HashMap<>();
+        for (EncryptedChunk chunk : chunks) {
+            chunkMap.put(chunk.key(), chunk);
+        }
+        return chunkMap;
+    }
+
+    private ChunksMetadataProto.ChunksMetadata buildMetadata(
+            SecretKey tertiaryKey, ChunksMetadataProto.ChunkOrdering chunkOrdering)
+            throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
+                    InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    ShortBufferException, NoSuchPaddingException {
+        ChunksMetadataProto.ChunksMetadata metaData = new ChunksMetadataProto.ChunksMetadata();
+        metaData.cipherType = ChunksMetadataProto.AES_256_GCM;
+        metaData.checksumType = ChunksMetadataProto.SHA_256;
+        metaData.chunkOrdering = encryptChunkOrdering(tertiaryKey, chunkOrdering);
+        return metaData;
+    }
+
+    private byte[] encryptChunkOrdering(
+            SecretKey tertiaryKey, ChunksMetadataProto.ChunkOrdering chunkOrdering)
+            throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException, ShortBufferException {
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+
+        byte[] nonce = generateNonce();
+
+        cipher.init(
+                Cipher.ENCRYPT_MODE,
+                tertiaryKey,
+                new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, nonce));
+
+        byte[] orderingBytes = ChunksMetadataProto.ChunkOrdering.toByteArray(chunkOrdering);
+        // We prepend the nonce to the ordering.
+        byte[] output =
+                Arrays.copyOf(
+                        nonce,
+                        GCM_NONCE_LENGTH_BYTES + orderingBytes.length + GCM_TAG_LENGTH_BYTES);
+
+        cipher.doFinal(
+                orderingBytes,
+                /*inputOffset=*/ 0,
+                /*inputLen=*/ orderingBytes.length,
+                output,
+                /*outputOffset=*/ GCM_NONCE_LENGTH_BYTES);
+
+        return output;
+    }
+
+    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/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/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
new file mode 100644
index 0000000..1d0224d
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.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 com.android.server.backup.encryption.transport;
+
+import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.DelegatingTransport;
+import com.android.server.backup.transport.TransportClient;
+
+/**
+ * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
+ * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
+ * TransportClient.connect(String)}.
+ */
+public class IntermediateEncryptingTransport extends DelegatingTransport {
+    private final TransportClient mTransportClient;
+    private final Object mConnectLock = new Object();
+    private volatile IBackupTransport mRealTransport;
+
+    @VisibleForTesting
+    IntermediateEncryptingTransport(TransportClient transportClient) {
+        mTransportClient = transportClient;
+    }
+
+    @Override
+    protected IBackupTransport getDelegate() throws RemoteException {
+        if (mRealTransport == null) {
+            connect();
+        }
+        return mRealTransport;
+    }
+
+    private void connect() throws RemoteException {
+        Log.i(TAG, "connecting " + mTransportClient);
+        synchronized (mConnectLock) {
+            if (mRealTransport == null) {
+                mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport");
+                if (mRealTransport == null) {
+                    throw new RemoteException("Could not connect: " + mTransportClient);
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    TransportClient getClient() {
+        return mTransportClient;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
new file mode 100644
index 0000000..6e6d571
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.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.backup.encryption.transport;
+
+import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportStats;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
+ */
+public class IntermediateEncryptingTransportManager {
+    private static final String CALLER = "IntermediateEncryptingTransportManager";
+    private final TransportClientManager mTransportClientManager;
+    private final Object mTransportsLock = new Object();
+    private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
+
+    @VisibleForTesting
+    IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
+        mTransportClientManager = transportClientManager;
+    }
+
+    public IntermediateEncryptingTransportManager(Context context) {
+        this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
+    }
+
+    /**
+     * Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and
+     * provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
+     * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
+     * the real {@link IBackupTransport}.
+     * @param intent {@link Intent} created with a call to {@link
+     * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
+     * @return
+     */
+    public IntermediateEncryptingTransport get(Intent intent) {
+        Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
+        Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
+        synchronized (mTransportsLock) {
+            return mTransports.computeIfAbsent(transportIntent.getComponent(),
+                    c -> create(transportIntent));
+        }
+    }
+
+    /**
+     * Create an instance of {@link IntermediateEncryptingTransport}.
+     */
+    private IntermediateEncryptingTransport create(Intent realTransportIntent) {
+        Log.d(TAG, "create: intent:" + realTransportIntent);
+        return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
+                realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
+    }
+
+    /**
+     * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
+     * {@link #get(Intent)} with this {@link Intent}.
+     */
+    public void cleanup(Intent intent) {
+        Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
+        Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent);
+
+        IntermediateEncryptingTransport transport;
+        synchronized (mTransportsLock) {
+            transport = mTransports.remove(transportIntent.getComponent());
+        }
+        if (transport != null) {
+            mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER);
+        } else {
+            Log.i(TAG, "Could not find IntermediateEncryptingTransport");
+        }
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp
index 3376ec9..2a36dcf 100644
--- a/packages/BackupEncryption/test/robolectric/Android.bp
+++ b/packages/BackupEncryption/test/robolectric/Android.bp
@@ -16,13 +16,19 @@
     name: "BackupEncryptionRoboTests",
     srcs: [
         "src/**/*.java",
-        ":FrameworksServicesRoboShadows",
+//        ":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",
 }
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/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
index 24e5573..c5f78c2 100644
--- 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
@@ -19,10 +19,8 @@
 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.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
 
 import com.google.common.base.Charsets;
 
@@ -31,167 +29,86 @@
 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 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 ChunkHash mChunkHashA;
-    private ChunkHash mChunkHashB;
-    private ChunkHash mChunkHashC;
+    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() throws Exception {
-        mChunkHashA = getHash(CHUNK_A);
-        mChunkHashB = getHash(CHUNK_B);
-        mChunkHashC = getHash(CHUNK_C);
+    public void setUp() {
+        mChunkListingMap = createFromFixture();
     }
 
     @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();
+    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 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();
+    public void hasChunk_isFalseForNonexistentChunks() {
+        assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse();
+        assertThat(mChunkListingMap.hasChunk(getHash(""))).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);
+    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 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);
+    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 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();
+    public void getChunkListing_isNullForNonExistentChunks() {
+        assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).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);
+    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);
     }
 
-    @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) {
+    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/ChunkTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkTest.java
deleted file mode 100644
index 1796f56..0000000
--- a/packages/BackupEncryption/test/robolectric/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/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/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
index 634acdc..7e1fded 100644
--- 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
@@ -24,7 +24,7 @@
 import android.platform.test.annotations.Presubmit;
 
 import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
 
 import org.junit.Before;
 import org.junit.Test;
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
index d231603..6f58ee1 100644
--- 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
@@ -24,7 +24,7 @@
 import android.platform.test.annotations.Presubmit;
 
 import com.android.server.backup.encryption.chunk.ChunkHash;
-import com.android.server.backup.encryption.chunk.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
 
 import org.junit.Before;
 import org.junit.Test;
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/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/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/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java
new file mode 100644
index 0000000..f6914ef
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java
@@ -0,0 +1,397 @@
+/*
+ * 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.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.encryption.protos.nano.ChunksMetadataProto.SHA_256;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.BackupFileBuilder;
+import com.android.server.backup.encryption.chunking.EncryptedChunk;
+import com.android.server.backup.encryption.chunking.EncryptedChunkEncoder;
+import com.android.server.backup.encryption.chunking.LengthlessEncryptedChunkEncoder;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.TertiaryKeyGenerator;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey;
+import com.android.server.backup.encryption.tasks.BackupEncrypter.Result;
+import com.android.server.backup.testing.CryptoTestUtils;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.protobuf.nano.MessageNano;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.concurrent.CancellationException;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class})
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class EncryptedBackupTaskTest {
+
+    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 int BITS_PER_BYTE = 8;
+
+    private static final byte[] TEST_FINGERPRINT_MIXER_SALT =
+            Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES);
+
+    private static final byte[] TEST_NONCE =
+            Arrays.copyOf(new byte[] {55}, EncryptedChunk.NONCE_LENGTH_BYTES);
+
+    private static final ChunkHash TEST_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_3 =
+            new ChunkHash(Arrays.copyOf(new byte[] {3}, ChunkHash.HASH_LENGTH_BYTES));
+
+    private static final EncryptedChunk TEST_CHUNK_1 =
+            EncryptedChunk.create(TEST_HASH_1, TEST_NONCE, new byte[] {1, 2, 3, 4, 5});
+    private static final EncryptedChunk TEST_CHUNK_2 =
+            EncryptedChunk.create(TEST_HASH_2, TEST_NONCE, new byte[] {6, 7, 8, 9, 10});
+    private static final EncryptedChunk TEST_CHUNK_3 =
+            EncryptedChunk.create(TEST_HASH_3, TEST_NONCE, new byte[] {11, 12, 13, 14, 15});
+
+    private static final byte[] TEST_CHECKSUM = Arrays.copyOf(new byte[] {10}, 258 / 8);
+    private static final String TEST_PACKAGE_NAME = "com.example.package";
+    private static final String TEST_OLD_DOCUMENT_ID = "old_doc_1";
+    private static final String TEST_NEW_DOCUMENT_ID = "new_doc_1";
+
+    @Captor private ArgumentCaptor<ChunksMetadata> mMetadataCaptor;
+
+    @Mock private CryptoBackupServer mCryptoBackupServer;
+    @Mock private BackupEncrypter mBackupEncrypter;
+    @Mock private BackupFileBuilder mBackupFileBuilder;
+
+    private ChunkListing mOldChunkListing;
+    private SecretKey mTertiaryKey;
+    private WrappedKey mWrappedTertiaryKey;
+    private EncryptedChunkEncoder mEncryptedChunkEncoder;
+    private EncryptedBackupTask mTask;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        SecureRandom secureRandom = new SecureRandom();
+        mTertiaryKey = new TertiaryKeyGenerator(secureRandom).generate();
+        mWrappedTertiaryKey = new WrappedKey();
+
+        mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder();
+
+        ShadowBackupFileBuilder.sInstance = mBackupFileBuilder;
+
+        mTask =
+                new EncryptedBackupTask(
+                        mCryptoBackupServer, secureRandom, TEST_PACKAGE_NAME, mBackupEncrypter);
+    }
+
+    @Test
+    public void performNonIncrementalBackup_performsBackup() throws Exception {
+        setUpWithoutExistingBackup();
+
+        // Chunk listing and ordering don't matter for this test.
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing());
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any()))
+                .thenReturn(TEST_NEW_DOCUMENT_ID);
+
+        mTask.performNonIncrementalBackup(
+                mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT);
+
+        verify(mBackupFileBuilder)
+                .writeChunks(
+                        ImmutableList.of(TEST_HASH_1, TEST_HASH_2),
+                        ImmutableMap.of(TEST_HASH_1, TEST_CHUNK_1, TEST_HASH_2, TEST_CHUNK_2));
+        verify(mBackupFileBuilder).finish(any());
+        verify(mCryptoBackupServer)
+                .uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), eq(mWrappedTertiaryKey));
+    }
+
+    @Test
+    public void performIncrementalBackup_performsBackup() throws Exception {
+        setUpWithExistingBackup();
+
+        // Chunk listing and ordering don't matter for this test.
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing());
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        when(mCryptoBackupServer.uploadIncrementalBackup(
+                        eq(TEST_PACKAGE_NAME), eq(TEST_OLD_DOCUMENT_ID), any(), any()))
+                .thenReturn(TEST_NEW_DOCUMENT_ID);
+
+        mTask.performIncrementalBackup(mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing);
+
+        verify(mBackupFileBuilder)
+                .writeChunks(
+                        ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3),
+                        ImmutableMap.of(TEST_HASH_2, TEST_CHUNK_2));
+        verify(mBackupFileBuilder).finish(any());
+        verify(mCryptoBackupServer)
+                .uploadIncrementalBackup(
+                        eq(TEST_PACKAGE_NAME),
+                        eq(TEST_OLD_DOCUMENT_ID),
+                        any(),
+                        eq(mWrappedTertiaryKey));
+    }
+
+    @Test
+    public void performIncrementalBackup_returnsNewChunkListingWithDocId() throws Exception {
+        setUpWithExistingBackup();
+
+        ChunkListing chunkListingWithoutDocId =
+                CryptoTestUtils.newChunkListingWithoutDocId(
+                        TEST_FINGERPRINT_MIXER_SALT,
+                        AES_256_GCM,
+                        CHUNK_ORDERING_TYPE_UNSPECIFIED,
+                        createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1),
+                        createChunkProtoFor(TEST_HASH_2, TEST_CHUNK_2));
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(chunkListingWithoutDocId);
+
+        // Chunk ordering doesn't matter for this test.
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        when(mCryptoBackupServer.uploadIncrementalBackup(
+                        eq(TEST_PACKAGE_NAME), eq(TEST_OLD_DOCUMENT_ID), any(), any()))
+                .thenReturn(TEST_NEW_DOCUMENT_ID);
+
+        ChunkListing actualChunkListing =
+                mTask.performIncrementalBackup(mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing);
+
+        ChunkListing expectedChunkListing = CryptoTestUtils.clone(chunkListingWithoutDocId);
+        expectedChunkListing.documentId = TEST_NEW_DOCUMENT_ID;
+        assertChunkListingsAreEqual(actualChunkListing, expectedChunkListing);
+    }
+
+    @Test
+    public void performNonIncrementalBackup_returnsNewChunkListingWithDocId() throws Exception {
+        setUpWithoutExistingBackup();
+
+        ChunkListing chunkListingWithoutDocId =
+                CryptoTestUtils.newChunkListingWithoutDocId(
+                        TEST_FINGERPRINT_MIXER_SALT,
+                        AES_256_GCM,
+                        CHUNK_ORDERING_TYPE_UNSPECIFIED,
+                        createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1),
+                        createChunkProtoFor(TEST_HASH_2, TEST_CHUNK_2));
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(chunkListingWithoutDocId);
+
+        // Chunk ordering doesn't matter for this test.
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any()))
+                .thenReturn(TEST_NEW_DOCUMENT_ID);
+
+        ChunkListing actualChunkListing =
+                mTask.performNonIncrementalBackup(
+                        mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT);
+
+        ChunkListing expectedChunkListing = CryptoTestUtils.clone(chunkListingWithoutDocId);
+        expectedChunkListing.documentId = TEST_NEW_DOCUMENT_ID;
+        assertChunkListingsAreEqual(actualChunkListing, expectedChunkListing);
+    }
+
+    @Test
+    public void performNonIncrementalBackup_buildsCorrectChunkMetadata() throws Exception {
+        setUpWithoutExistingBackup();
+
+        // Chunk listing doesn't matter for this test.
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing());
+
+        ChunkOrdering expectedOrdering =
+                CryptoTestUtils.newChunkOrdering(new int[10], TEST_CHECKSUM);
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(expectedOrdering);
+
+        when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any()))
+                .thenReturn(TEST_NEW_DOCUMENT_ID);
+
+        mTask.performNonIncrementalBackup(
+                mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT);
+
+        verify(mBackupFileBuilder).finish(mMetadataCaptor.capture());
+
+        ChunksMetadata actualMetadata = mMetadataCaptor.getValue();
+        assertThat(actualMetadata.checksumType).isEqualTo(SHA_256);
+        assertThat(actualMetadata.cipherType).isEqualTo(AES_256_GCM);
+
+        ChunkOrdering actualOrdering = decryptChunkOrdering(actualMetadata.chunkOrdering);
+        assertThat(actualOrdering.checksum).isEqualTo(TEST_CHECKSUM);
+        assertThat(actualOrdering.starts).isEqualTo(expectedOrdering.starts);
+    }
+
+    @Test
+    public void cancel_incrementalBackup_doesNotUploadOrSaveChunkListing() throws Exception {
+        setUpWithExistingBackup();
+
+        // Chunk listing and ordering don't matter for this test.
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing());
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        mTask.cancel();
+        assertThrows(
+                CancellationException.class,
+                () ->
+                        mTask.performIncrementalBackup(
+                                mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing));
+
+        verify(mCryptoBackupServer, never()).uploadIncrementalBackup(any(), any(), any(), any());
+        verify(mCryptoBackupServer, never()).uploadNonIncrementalBackup(any(), any(), any());
+    }
+
+    @Test
+    public void cancel_nonIncrementalBackup_doesNotUploadOrSaveChunkListing() throws Exception {
+        setUpWithoutExistingBackup();
+
+        // Chunk listing and ordering don't matter for this test.
+        when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing());
+        when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering());
+
+        mTask.cancel();
+        assertThrows(
+                CancellationException.class,
+                () ->
+                        mTask.performNonIncrementalBackup(
+                                mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT));
+
+        verify(mCryptoBackupServer, never()).uploadIncrementalBackup(any(), any(), any(), any());
+        verify(mCryptoBackupServer, never()).uploadNonIncrementalBackup(any(), any(), any());
+    }
+
+    /** Sets up a backup of [CHUNK 1][CHUNK 2] with no existing data. */
+    private void setUpWithoutExistingBackup() throws Exception {
+        Result result =
+                new Result(
+                        ImmutableList.of(TEST_HASH_1, TEST_HASH_2),
+                        ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_2),
+                        TEST_CHECKSUM);
+        when(mBackupEncrypter.backup(any(), eq(TEST_FINGERPRINT_MIXER_SALT), eq(ImmutableSet.of())))
+                .thenReturn(result);
+    }
+
+    /**
+     * Sets up a backup of [CHUNK 1][CHUNK 2][CHUNK 3] where the previous backup contained [CHUNK
+     * 1][CHUNK 3].
+     */
+    private void setUpWithExistingBackup() throws Exception {
+        mOldChunkListing =
+                CryptoTestUtils.newChunkListing(
+                        TEST_OLD_DOCUMENT_ID,
+                        TEST_FINGERPRINT_MIXER_SALT,
+                        AES_256_GCM,
+                        CHUNK_ORDERING_TYPE_UNSPECIFIED,
+                        createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1),
+                        createChunkProtoFor(TEST_HASH_3, TEST_CHUNK_3));
+
+        Result result =
+                new Result(
+                        ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3),
+                        ImmutableList.of(TEST_CHUNK_2),
+                        TEST_CHECKSUM);
+        when(mBackupEncrypter.backup(
+                        any(),
+                        eq(TEST_FINGERPRINT_MIXER_SALT),
+                        eq(ImmutableSet.of(TEST_HASH_1, TEST_HASH_3))))
+                .thenReturn(result);
+    }
+
+    private ChunksMetadataProto.Chunk createChunkProtoFor(
+            ChunkHash chunkHash, EncryptedChunk encryptedChunk) {
+        return CryptoTestUtils.newChunk(
+                chunkHash, mEncryptedChunkEncoder.getEncodedLengthOfChunk(encryptedChunk));
+    }
+
+    private ChunkOrdering decryptChunkOrdering(byte[] encryptedOrdering) throws Exception {
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(
+                Cipher.DECRYPT_MODE,
+                mTertiaryKey,
+                new GCMParameterSpec(
+                        GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE,
+                        encryptedOrdering,
+                        /*offset=*/ 0,
+                        GCM_NONCE_LENGTH_BYTES));
+        byte[] decrypted =
+                cipher.doFinal(
+                        encryptedOrdering,
+                        GCM_NONCE_LENGTH_BYTES,
+                        encryptedOrdering.length - GCM_NONCE_LENGTH_BYTES);
+        return ChunkOrdering.parseFrom(decrypted);
+    }
+
+    // This method is needed because nano protobuf generated classes dont implmenent
+    // .equals
+    private void assertChunkListingsAreEqual(ChunkListing a, ChunkListing b) {
+        byte[] aBytes = MessageNano.toByteArray(a);
+        byte[] bBytes = MessageNano.toByteArray(b);
+
+        assertThat(aBytes).isEqualTo(bBytes);
+    }
+
+    @Implements(BackupFileBuilder.class)
+    public static class ShadowBackupFileBuilder {
+
+        private static BackupFileBuilder sInstance;
+
+        @Implementation
+        public static BackupFileBuilder createForNonIncremental(OutputStream outputStream) {
+            return sInstance;
+        }
+
+        @Implementation
+        public static BackupFileBuilder createForIncremental(
+                OutputStream outputStream, ChunkListing oldChunkListing) {
+            return sInstance;
+        }
+    }
+}
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/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
index 3f3494d..b9055ce 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
@@ -16,7 +16,11 @@
 
 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;
@@ -42,4 +46,72 @@
         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;
+    }
+
+    public static ChunksMetadataProto.ChunkListing newChunkListing(
+            String docId,
+            byte[] fingerprintSalt,
+            int cipherType,
+            int orderingType,
+            ChunksMetadataProto.Chunk... chunks) {
+        ChunksMetadataProto.ChunkListing chunkListing =
+                newChunkListingWithoutDocId(fingerprintSalt, cipherType, orderingType, chunks);
+        chunkListing.documentId = docId;
+        return chunkListing;
+    }
+
+    public static ChunksMetadataProto.ChunkListing newChunkListingWithoutDocId(
+            byte[] fingerprintSalt,
+            int cipherType,
+            int orderingType,
+            ChunksMetadataProto.Chunk... chunks) {
+        ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+        chunkListing.fingerprintMixerSalt = Arrays.copyOf(fingerprintSalt, fingerprintSalt.length);
+        chunkListing.cipherType = cipherType;
+        chunkListing.chunkOrderingType = orderingType;
+        chunkListing.chunks = chunks;
+        return chunkListing;
+    }
+
+    public static ChunksMetadataProto.ChunkOrdering newChunkOrdering(
+            int[] starts, byte[] checksum) {
+        ChunksMetadataProto.ChunkOrdering chunkOrdering = new ChunksMetadataProto.ChunkOrdering();
+        chunkOrdering.starts = Arrays.copyOf(starts, starts.length);
+        chunkOrdering.checksum = Arrays.copyOf(checksum, checksum.length);
+        return chunkOrdering;
+    }
+
+    public static ChunksMetadataProto.ChunkListing clone(
+            ChunksMetadataProto.ChunkListing original) {
+        ChunksMetadataProto.Chunk[] clonedChunks;
+        if (original.chunks == null) {
+            clonedChunks = null;
+        } else {
+            clonedChunks = new ChunksMetadataProto.Chunk[original.chunks.length];
+            for (int i = 0; i < original.chunks.length; i++) {
+                clonedChunks[i] = clone(original.chunks[i]);
+            }
+        }
+
+        return newChunkListing(
+                original.documentId,
+                original.fingerprintMixerSalt,
+                original.cipherType,
+                original.chunkOrderingType,
+                clonedChunks);
+    }
+
+    public static ChunksMetadataProto.Chunk clone(ChunksMetadataProto.Chunk original) {
+        return newChunk(original.hash, original.length);
+    }
 }
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/packages/BackupEncryption/test/unittest/Android.bp b/packages/BackupEncryption/test/unittest/Android.bp
new file mode 100644
index 0000000..d7c510b
--- /dev/null
+++ b/packages/BackupEncryption/test/unittest/Android.bp
@@ -0,0 +1,22 @@
+android_test {
+    name: "BackupEncryptionUnitTests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "truth-prebuilt",
+        "testables",
+        "testng",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+        "BackupEncryption",
+    ],
+    test_suites: ["device-tests"],
+    instrumentation_for: "BackupEncryption",
+    certificate: "platform",
+}
\ No newline at end of file
diff --git a/packages/BackupEncryption/test/unittest/AndroidManifest.xml b/packages/BackupEncryption/test/unittest/AndroidManifest.xml
new file mode 100644
index 0000000..39ac8aa
--- /dev/null
+++ b/packages/BackupEncryption/test/unittest/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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"
+          package="com.android.server.backup.encryption.unittests"
+          android:sharedUserId="android.uid.system" >
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.backup.encryption"
+        android:label="Backup Encryption Unit Tests" />
+</manifest>
\ No newline at end of file
diff --git a/packages/BackupEncryption/test/unittest/AndroidTest.xml b/packages/BackupEncryption/test/unittest/AndroidTest.xml
new file mode 100644
index 0000000..c9c812a
--- /dev/null
+++ b/packages/BackupEncryption/test/unittest/AndroidTest.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.
+  -->
+<configuration description="Runs Backup Encryption Unit Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="BackupEncryptionUnitTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="BackupEncryptionUnitTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.backup.encryption.unittests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java
new file mode 100644
index 0000000..0d43a19
--- /dev/null
+++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.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.server.backup.encryption.transport;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotSame;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+
+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 IntermediateEncryptingTransportManagerTest {
+    @Mock private TransportClient mTransportClient;
+    @Mock private TransportClientManager mTransportClientManager;
+
+    private final ComponentName mTransportComponent = new ComponentName("pkg", "class");
+    private final Bundle mExtras = new Bundle();
+    private Intent mEncryptingTransportIntent;
+    private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mExtras.putInt("test", 1);
+        mEncryptingTransportIntent =
+                TransportClientManager.getEncryptingTransportIntent(mTransportComponent)
+                        .putExtras(mExtras);
+        mIntermediateEncryptingTransportManager =
+                new IntermediateEncryptingTransportManager(mTransportClientManager);
+    }
+
+    @Test
+    public void testGet_createsClientWithRealTransportComponentAndExtras() {
+        when(mTransportClientManager.getTransportClient(any(), any(), any()))
+                .thenReturn(mTransportClient);
+
+        IntermediateEncryptingTransport intermediateEncryptingTransport =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+
+        assertEquals(mTransportClient, intermediateEncryptingTransport.getClient());
+        verify(mTransportClientManager, times(1))
+                .getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any());
+        verifyNoMoreInteractions(mTransportClientManager);
+    }
+
+    @Test
+    public void testGet_callTwice_returnsSameTransport() {
+        IntermediateEncryptingTransport transport1 =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+        IntermediateEncryptingTransport transport2 =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+
+        assertEquals(transport1, transport2);
+    }
+
+    @Test
+    public void testCleanup_disposesTransportClient() {
+        when(mTransportClientManager.getTransportClient(any(), any(), any()))
+                .thenReturn(mTransportClient);
+
+        IntermediateEncryptingTransport transport =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+        mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
+
+        verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any());
+        verify(mTransportClientManager, times(1))
+                .disposeOfTransportClient(eq(mTransportClient), any());
+        verifyNoMoreInteractions(mTransportClientManager);
+    }
+
+    @Test
+    public void testCleanup_removesCachedTransport() {
+        when(mTransportClientManager.getTransportClient(any(), any(), any()))
+                .thenReturn(mTransportClient);
+
+        IntermediateEncryptingTransport transport1 =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+        mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
+        IntermediateEncryptingTransport transport2 =
+                mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
+
+        assertNotSame(transport1, transport2);
+    }
+}
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
new file mode 100644
index 0000000..cc4b0ab
--- /dev/null
+++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.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.transport;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.anyString;
+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.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.TransportClient;
+
+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 IntermediateEncryptingTransportTest {
+    @Mock private IBackupTransport mRealTransport;
+    @Mock private TransportClient mTransportClient;
+
+    private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
+    }
+
+    @Test
+    public void testGetDelegate_callsConnect() throws Exception {
+        when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
+
+        IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
+
+        assertEquals(mRealTransport, ret);
+        verify(mTransportClient, times(1)).connect(anyString());
+        verifyNoMoreInteractions(mTransportClient);
+    }
+
+    @Test
+    public void testGetDelegate_callTwice_callsConnectOnce() throws Exception {
+        when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
+
+        IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate();
+        IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate();
+
+        assertEquals(mRealTransport, ret1);
+        assertEquals(mRealTransport, ret2);
+        verify(mTransportClient, times(1)).connect(anyString());
+        verifyNoMoreInteractions(mTransportClient);
+    }
+}
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/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/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index 27146fb..264b7d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -25,8 +25,6 @@
         modules = {
                 DependencyProvider.class,
                 DependencyBinder.class,
-                ServiceBinder.class,
-                SystemUIBinder.class,
                 SystemUIFactory.ContextHolder.class,
                 SystemUIModule.class,
                 CarSystemUIModule.class
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/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-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 0073003..8f72ab6 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/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>
@@ -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-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-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/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 15c8367..d398aa5 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -1,7 +1,9 @@
-java_library {
+android_library {
     name: "SettingsLib-search",
-    host_supported: true,
     srcs: ["src/**/*.java"],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
 }
 
 java_plugin {
@@ -9,9 +11,11 @@
     processor_class: "com.android.settingslib.search.IndexableProcessor",
     static_libs: [
         "javapoet-prebuilt-jar",
-        "SettingsLib-search",
     ],
-    srcs: ["processor-src/**/*.java"],
+    srcs: [
+        "processor-src/**/*.java",
+        "src/com/android/settingslib/search/SearchIndexable.java"
+    ],
     java_resource_dirs: ["resources"],
 }
 
diff --git a/packages/SettingsLib/search/AndroidManifest.xml b/packages/SettingsLib/search/AndroidManifest.xml
new file mode 100644
index 0000000..9707283
--- /dev/null
+++ b/packages/SettingsLib/search/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?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"
+          package="com.android.settingslib.search">
+
+</manifest>
\ No newline at end of file
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index 10fc685..5dc9061 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -143,7 +143,7 @@
 
         final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
                 .addModifiers(Modifier.PUBLIC)
-                .addSuperinterface(ClassName.get(SearchIndexableResources.class))
+                .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
                 .addField(providers)
                 .addMethod(baseConstructorBuilder.build())
                 .addMethod(addIndex)
@@ -210,4 +210,4 @@
         mFiler = processingEnvironment.getFiler();
         mMessager = processingEnvironment.getMessager();
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/Indexable.java b/packages/SettingsLib/search/src/com/android/settingslib/search/Indexable.java
new file mode 100644
index 0000000..e68b0d1
--- /dev/null
+++ b/packages/SettingsLib/search/src/com/android/settingslib/search/Indexable.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.settingslib.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.List;
+
+/**
+ * Interface for classes whose instances can provide data for indexing.
+ *
+ * See {@link android.provider.SearchIndexableResource} and {@link SearchIndexableRaw}.
+ */
+public interface Indexable {
+
+    /**
+     * Interface for classes whose instances can provide data for indexing.
+     */
+    interface SearchIndexProvider {
+        /**
+         * Return a list of references for indexing.
+         *
+         * See {@link android.provider.SearchIndexableResource}
+         *
+         * @param context the context.
+         * @param enabled hint telling if the data needs to be considered into the search results
+         *                or not.
+         * @return a list of {@link android.provider.SearchIndexableResource} references.
+         * Can be null.
+         */
+        List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled);
+
+        /**
+         * Return a list of raw data for indexing. See {@link SearchIndexableRaw}
+         *
+         * @param context the context.
+         * @param enabled hint telling if the data needs to be considered into the search results
+         *                or not.
+         * @return a list of {@link SearchIndexableRaw} references. Can be null.
+         */
+        List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled);
+
+        /**
+         * Return a list of data keys that cannot be indexed. See {@link SearchIndexableRaw}
+         *
+         * @param context the context.
+         * @return a list of {@link SearchIndexableRaw} references. Can be null.
+         */
+        List<String> getNonIndexableKeys(Context context);
+    }
+}
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.java
new file mode 100644
index 0000000..021ca33
--- /dev/null
+++ b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableRaw.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.settingslib.search;
+
+import android.content.Context;
+import android.provider.SearchIndexableData;
+
+/**
+ * Indexable raw data for Search.
+ *
+ * This is the raw data used by the Indexer and should match its data model.
+ *
+ * See {@link Indexable} and {@link android.provider.SearchIndexableResource}.
+ */
+public class SearchIndexableRaw extends SearchIndexableData {
+
+    /**
+     * Title's raw data.
+     */
+    public String title;
+
+    /**
+     * Summary's raw data when the data is "ON".
+     */
+    public String summaryOn;
+
+    /**
+     * Summary's raw data when the data is "OFF".
+     */
+    public String summaryOff;
+
+    /**
+     * Entries associated with the raw data (when the data can have several values).
+     */
+    public String entries;
+
+    /**
+     * Keywords' raw data.
+     */
+    public String keywords;
+
+    /**
+     * Fragment's or Activity's title associated with the raw data.
+     */
+    public String screenTitle;
+
+    public SearchIndexableRaw(Context context) {
+        super(context);
+    }
+}
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableResources.java b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableResources.java
index 300d360..976647b 100644
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableResources.java
+++ b/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexableResources.java
@@ -32,4 +32,4 @@
      * as a device binary.
      */
     void addIndex(Class indexClass);
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 365923e..046ffc3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2242,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 32fc7ff..720266a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1521,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,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d7eb7e95..3a7de18 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -17,8 +17,7 @@
 package android.provider;
 
 import static com.google.android.collect.Sets.newHashSet;
-
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static java.lang.reflect.Modifier.isFinal;
 import static java.lang.reflect.Modifier.isPublic;
@@ -308,7 +307,6 @@
                     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,
@@ -656,7 +654,6 @@
                  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,
@@ -755,12 +752,11 @@
             Set<String> settings, Set<String> settingsToBackup, Set<String> blacklist) {
         Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
         Set<String> settingsNotBackedUpOrBlacklisted = difference(settingsNotBackedUp, blacklist);
-        assertTrue(
-                "Settings not backed up or blacklisted",
-                settingsNotBackedUpOrBlacklisted.isEmpty());
+        assertWithMessage("Settings not backed up or blacklisted")
+                .that(settingsNotBackedUpOrBlacklisted).isEmpty();
 
-        assertTrue(
-                "blacklisted settings backed up", intersect(settingsToBackup, blacklist).isEmpty());
+        assertWithMessage("blacklisted settings backed up")
+                .that(intersect(settingsToBackup, blacklist)).isEmpty();
     }
 
     private static Set<String> getCandidateSettings(
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 607409f..2a41aa6 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1378,9 +1378,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 d674be4..0c582c4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -76,6 +76,16 @@
     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",
@@ -160,39 +170,3 @@
     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"],
-    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/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/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/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 925e4fa..a1006a8 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -42,7 +42,6 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingHorizontal="24dp"
-        android:paddingBottom="48dp"
         android:paddingTop="8dp"
         android:gravity="@integer/biometric_dialog_text_gravity"
         android:textSize="16sp"
@@ -52,6 +51,7 @@
         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" />
 
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
index d83776b..23199aa 100644
--- a/packages/SystemUI/res/layout/auth_container_view.xml
+++ b/packages/SystemUI/res/layout/auth_container_view.xml
@@ -23,7 +23,8 @@
         android:id="@+id/background"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/biometric_dialog_dim_color"/>
+        android:background="@color/biometric_dialog_dim_color"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"/>
 
     <View
         android:id="@+id/panel"
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/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/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 57c934e..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>
@@ -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>
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 5480eab..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>
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 3e3696b..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>
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-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 4be1a0f..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>
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 c948328..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>
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 2b12149..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>
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 eaca944..37651a5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -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>
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 457206d..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>
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 38293bf..61210d3 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -480,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 3a1f7a3..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>
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/Android.bp b/packages/SystemUI/shared/Android.bp
index 31a538c..4471818 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -27,4 +27,6 @@
     // Enforce that the library is built against java 7 so that there are
     // no compatibility issues with launcher
     java_version: "1.7",
+
+    min_sdk_version: "26",
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 9228b17..1cabee1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -109,4 +109,9 @@
      * Ends the system screen pinning.
      */
     void stopScreenPinning() = 17;
+
+    /**
+     * Sets the shelf height and visibility.
+     */
+    void setShelfHeight(boolean visible, int shelfHeight) = 20;
 }
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..fe5a57a 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,123 @@
  * 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) {
-            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 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/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 794c30a..ad182fe 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 {
 
@@ -143,14 +143,6 @@
         }
     }
 
-    public void setShelfHeight(boolean visible, int shelfHeight) {
-        try {
-            WindowManagerGlobal.getWindowManagerService().setShelfHeight(visible, shelfHeight);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to set shelf height");
-        }
-    }
-
     public void setRecentsVisibility(boolean visible) {
         try {
             WindowManagerGlobal.getWindowManagerService().setRecentsVisibility(visible);
@@ -212,7 +204,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 +213,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/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 3b03d67..e1b723e 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -299,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
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index eaaa3ed..e3ac0f6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -86,13 +86,12 @@
             mSecurityMessageDisplay.setMessage("");
         }
         final boolean wasDisabled = mPasswordEntry.isEnabled();
-        // Don't set enabled password entry & showSoftInput when PasswordEntry is invisible or in
-        // pausing stage.
+        setPasswordEntryEnabled(true);
+        setPasswordEntryInputEnabled(true);
+        // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage.
         if (!mResumed || !mPasswordEntry.isVisibleToUser()) {
             return;
         }
-        setPasswordEntryEnabled(true);
-        setPasswordEntryInputEnabled(true);
         if (wasDisabled) {
             mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 35eb272..8d167e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -49,7 +49,7 @@
 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 {
@@ -94,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;
@@ -134,8 +134,8 @@
         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) {
@@ -272,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/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 5fe5792..2cf0f8d 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.Service;
 
 /**
@@ -23,6 +24,9 @@
  */
 public interface ContextComponentHelper {
     /** Turns a classname into an instance of the class or returns null. */
+    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. */
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
index cee21c1..9952632 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.Service;
 
 import java.util.Map;
@@ -29,18 +30,29 @@
  */
 @Singleton
 public class ContextComponentResolver implements ContextComponentHelper {
+    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<Activity>> activityCreators,
             Map<Class<?>, Provider<Service>> serviceCreators,
             Map<Class<?>, Provider<SystemUI>> systemUICreators) {
+        mActivityCreators = activityCreators;
         mServiceCreators = serviceCreators;
         mSystemUICreators = systemUICreators;
     }
 
     /**
+     * Looks up the Activity class name to see if Dagger has an instance of it.
+     */
+    @Override
+    public Activity resolveActivity(String className) {
+        return resolve(className, mActivityCreators);
+    }
+
+    /**
      * Looks up the Service class name to see if Dagger has an instance of it.
      */
     @Override
@@ -57,12 +69,12 @@
     }
 
     private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
-        for (Map.Entry<Class<?>, Provider<T>> p : creators.entrySet()) {
-            if (p.getKey().getName().equals(className)) {
-                return p.getValue().get();
-            }
+        try {
+            Class<?> clazz = Class.forName(className);
+            Provider<T> provider = creators.get(clazz);
+            return provider == null ? null : provider.get();
+        } catch (ClassNotFoundException e) {
+            return null;
         }
-
-        return null;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
index 6209c2c..a94952c 100644
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -19,7 +19,6 @@
 import android.animation.ArgbEvaluator;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.RectF;
@@ -28,7 +27,6 @@
 import android.view.ContextThemeWrapper;
 import android.view.View;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.settingslib.Utils;
 
 /**
@@ -109,7 +107,6 @@
         mPaint.setColor((int) ArgbEvaluator.getInstance().evaluate(darkIntensity,
                 mLightColor,
                 mDarkColor));
-        updateShadow();
         if (getVisibility() == VISIBLE) {
             invalidate();
         }
@@ -121,21 +118,6 @@
         canvas.drawPath(mPath, mPaint);
     }
 
-    private void updateShadow() {
-        if (ColorUtils.calculateLuminance(mPaint.getColor()) > 0.7f) {
-            mPaint.setShadowLayer(/** radius */ 5,/** shadowDx */ 0, /** shadowDy */ -1,
-                    /** color */ ColorUtils.setAlphaComponent(/** color */ Color.BLACK,
-                                                              /** alpha */ 102));
-        } else {
-            mPaint.setShadowLayer(/** radius */ 0, /** shadowDx */ 0, /** shadowDy */ 0,
-                    /** color */ Color.TRANSPARENT);
-        }
-
-        if (getVisibility() == VISIBLE) {
-            invalidate();
-        }
-    }
-
     private static float convertDpToPixel(float dp, Context context) {
         return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
                 / DisplayMetrics.DENSITY_DEFAULT);
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index bd5b9c7..37bb54c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,6 +15,7 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
@@ -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,10 +111,10 @@
 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;
@@ -220,7 +221,7 @@
     @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;
@@ -314,24 +315,15 @@
     @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<FalsingManager> mFalsingManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
+    @Inject Lazy<AlarmManager> mAlarmManager;
 
     @Inject
     public Dependency() {
     }
 
-
     /**
      * Initialize Depenency.
      */
-    public static void initDependencies(SystemUIRootComponent rootComponent) {
-        if (sDependency != null) {
-            return;
-        }
-        sDependency = new Dependency();
-        rootComponent.createDependency().createSystemUI(sDependency);
-        sDependency.start();
-    }
-
     protected void start() {
         // TODO: Think about ways to push these creation rules out of Dependency to cut down
         // on imports.
@@ -363,7 +355,7 @@
 
         mProviders.put(FlashlightController.class, mFlashlightController::get);
 
-        mProviders.put(KeyguardMonitor.class, mKeyguardMonitor::get);
+        mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get);
 
         mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
 
@@ -508,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
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 d46a86c..239cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -23,6 +23,7 @@
 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;
@@ -230,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 6fec92c..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(SystemUIFactory.getInstance().getRootComponent());
 
         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 131a0f8..e761a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -26,15 +26,10 @@
 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)
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index e8b5285..2c8324c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -16,15 +16,15 @@
 
 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 android.os.Process;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.app.AppComponentFactory;
 
 import javax.inject.Inject;
@@ -60,9 +60,6 @@
                         SystemUIFactory.createFromConfig(context);
                         SystemUIFactory.getInstance().getRootComponent().inject(
                                 SystemUIAppComponentFactory.this);
-                        Log.d(TAG, "Initialized during Application creation in Process "
-                                + Process.myPid() + ", Thread " + Process.myTid());
-                        Log.d(TAG, "mComponentHelper: " + mComponentHelper);
                     }
             );
         }
@@ -83,8 +80,6 @@
                         SystemUIFactory.createFromConfig(context);
                         SystemUIFactory.getInstance().getRootComponent().inject(
                                 contentProvider);
-                        Log.d(TAG, "Initialized during ContentProvider creation in Process "
-                                + Process.myPid() + ", Thread " + Process.myTid());
                     }
             );
         }
@@ -94,14 +89,26 @@
 
     @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);
+    }
+
+    @NonNull
+    @Override
     public Service instantiateServiceCompat(
             @NonNull ClassLoader cl, @NonNull String className, Intent intent)
             throws InstantiationException, IllegalAccessException, ClassNotFoundException {
         if (mComponentHelper == null) {
-            // Everything is about to crash if this is true, but that is inevitable. We either crash
-            // here or crash lower in the stack. Better to crash early!
-            Log.wtf(TAG, "Uninitialized mComponentHelper in Process" + Process.myPid() + ", Thread "
-                    + Process.myTid());
+            // 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 88d65903..4531c89 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -24,7 +24,7 @@
 import dagger.multibindings.IntoMap;
 
 /**
- * Services and Activities that are injectable should go here.
+ * SystemUI objects that are injectable should go here.
  */
 @Module
 public abstract class SystemUIBinder {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index cd16d85..530dcdc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -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;
@@ -102,7 +101,10 @@
 
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
-        Dependency.initDependencies(mRootComponent);
+
+        Dependency dependency = new Dependency();
+        mRootComponent.createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 
     protected void initWithRootComponent(@NonNull SystemUIRootComponent rootComponent) {
@@ -133,10 +135,11 @@
             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),
+                expansionCallback, keyguardStateController,
                 Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
@@ -146,9 +149,9 @@
             LockscreenWallpaper lockscreenWallpaper,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
-            AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
+            AlarmManager alarmManager, KeyguardStateController keyguardStateController) {
         return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
-                scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
+                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 3395014..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
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
index 6ce673b..c70b2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
@@ -37,8 +37,6 @@
 @Component(modules = {
         DependencyProvider.class,
         DependencyBinder.class,
-        ServiceBinder.class,
-        SystemUIBinder.class,
         SystemUIFactory.ContextHolder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 1c5e800..8f1fcae 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -84,7 +84,7 @@
             }
         } else {
             String svc = args[0].toLowerCase();
-            if (Dependency.class.getName().endsWith(svc)) {
+            if (Dependency.class.getName().toLowerCase().endsWith(svc)) {
                 Dependency.staticDump(fd, pw, args);
             }
             for (SystemUI ui: services) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 38ca708..4516996 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
@@ -205,7 +192,7 @@
         try {
             setBehavior(AssistHandleBehavior.valueOf(behavior));
         } catch (IllegalArgumentException | NullPointerException e) {
-            Log.e(TAG, "Invalid behavior: " + behavior, e);
+            Log.e(TAG, "Invalid behavior: " + behavior);
         }
     }
 
@@ -241,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);
             }
         }
     }
@@ -256,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);
         }
     }
 
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 5dd3cb1..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
@@ -218,20 +269,18 @@
 
     @Override
     public void onAssistHandlesRequested() {
-        if (mAssistHandleCallbacks != null && !mIsDozing && !mIsNavBarHidden && !mOnLockscreen) {
+        if (mAssistHandleCallbacks != null
+                && isFullyAwake()
+                && !mIsNavBarHidden
+                && !mOnLockscreen) {
             mAssistHandleCallbacks.showAndGo();
         }
     }
 
-    private static boolean isNavBarHidden(int sysuiStateFlags) {
-        return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
-    }
-
     @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;
         }
@@ -270,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;
@@ -287,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;
         }
@@ -322,7 +382,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
+        if (!isFullyAwake() || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
             mAssistHandleCallbacks.hide();
         } else if (justUnlocked) {
             long currentEpochDay = LocalDate.now().toEpochDay();
@@ -344,7 +404,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || isSuppressed()) {
+        if (!isFullyAwake() || mIsNavBarHidden || isSuppressed()) {
             mAssistHandleCallbacks.hide();
         } else if (mOnLockscreen) {
             mAssistHandleCallbacks.showAndStay();
@@ -374,24 +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(this::recordLearnTimeElapsed);
-    }
-
-    private void recordLearnTimeElapsed() {
-        if (mContext != null) {
-            Settings.Secure.putLong(
-                    mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed);
-        }
+        mHandler.post(() -> Settings.Secure.putLong(
+                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
     }
 
     private void resetConsecutiveTaskSwitches() {
@@ -404,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,
@@ -463,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 eb60b3d..1f950f6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -21,7 +21,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -157,17 +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);
-        final HandlerThread assistHandleThread = new HandlerThread("AssistHandleThread");
-        assistHandleThread.start();
-        mHandleController = new AssistHandleBehaviorController(
-                context, mAssistUtils, assistHandleThread.getThreadHandler());
+        mHandleController = handleController;
 
         registerVoiceInteractionSessionListener();
         mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
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
index a68b61e..1d47fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -111,8 +111,12 @@
                         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) {
@@ -125,6 +129,8 @@
                         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);
             }
@@ -163,6 +169,11 @@
     }
 
     @Override
+    protected boolean supportsSmallDialog() {
+        return true;
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mIconController = new IconController(mContext, mIconView, mIndicatorView);
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
index df14a17..73bbce9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -16,6 +16,8 @@
 
 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;
@@ -32,6 +34,8 @@
 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;
@@ -92,6 +96,7 @@
         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
@@ -136,10 +141,15 @@
         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;
 
@@ -186,15 +196,14 @@
      */
     protected abstract void handleResetAfterHelp();
 
-    private final Runnable mResetErrorRunnable = () -> {
-        updateState(getStateForAfterError());
-        handleResetAfterError();
-    };
+    /**
+     * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
+     */
+    protected abstract boolean supportsSmallDialog();
 
-    private final Runnable mResetHelpRunnable = () -> {
-        updateState(STATE_AUTHENTICATING);
-        handleResetAfterHelp();
-    };
+    private final Runnable mResetErrorRunnable;
+
+    private final Runnable mResetHelpRunnable;
 
     private final OnClickListener mBackgroundClickListener = (view) -> {
         if (mState == STATE_AUTHENTICATED) {
@@ -226,6 +235,20 @@
 
         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) {
@@ -250,7 +273,7 @@
 
     @VisibleForTesting
     void updateSize(@AuthDialog.DialogSize int newSize) {
-        Log.v(TAG, "Current: " + mSize + " New: " + newSize);
+        Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
         if (newSize == AuthDialog.SIZE_SMALL) {
             mTitleView.setVisibility(View.GONE);
             mSubtitleView.setVisibility(View.GONE);
@@ -323,6 +346,8 @@
                     super.onAnimationEnd(animation);
                     mSize = newSize;
                     mDialogSizeAnimating = false;
+                    Utils.notifyAccessibilityContentChanged(mAccessibilityManager,
+                            AuthBiometricView.this);
                 }
             });
 
@@ -338,6 +363,7 @@
         } else {
             Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
         }
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
 
     public void updateState(@BiometricState int newState) {
@@ -359,6 +385,8 @@
                     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;
@@ -385,6 +413,7 @@
                 break;
         }
 
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
         mState = newState;
     }
 
@@ -406,8 +435,18 @@
         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);
@@ -441,6 +480,8 @@
         } else {
             view.setText(string);
         }
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
 
     private void setText(TextView view, String string) {
@@ -459,6 +500,8 @@
         mIndicatorView.setTextColor(mTextColorError);
         mIndicatorView.setVisibility(View.VISIBLE);
         mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
     }
 
     @Override
@@ -497,6 +540,7 @@
             updateState(STATE_AUTHENTICATING);
             mCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN);
             mTryAgainButton.setVisibility(View.GONE);
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
         });
     }
 
@@ -527,15 +571,6 @@
             // Restore positive button state
             mTryAgainButton.setVisibility(
                     mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
-
-            // Restore indicator text state
-            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);
-            }
         }
     }
 
@@ -573,7 +608,10 @@
                         MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
                         MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
             }
-            totalHeight += child.getMeasuredHeight();
+
+            if (child.getVisibility() != View.GONE) {
+                totalHeight += child.getMeasuredHeight();
+            }
         }
 
         // Use the new width so it's centered horizontally
@@ -600,9 +638,20 @@
         if (mIconOriginalY == 0) {
             mIconOriginalY = mIconView.getY();
             if (mSavedState == null) {
-                updateSize(mRequireConfirmation ? AuthDialog.SIZE_MEDIUM : AuthDialog.SIZE_SMALL);
+                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
index e198060..6555c75 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -19,12 +19,16 @@
 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.Handler;
 import android.os.IBinder;
+import android.os.UserManager;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -55,12 +59,12 @@
     private static final int ANIMATION_DURATION_SHOW_MS = 250;
     private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
 
-    private static final int STATE_UNKNOWN = 0;
-    private static final int STATE_ANIMATING_IN = 1;
-    private static final int STATE_PENDING_DISMISS = 2;
-    private static final int STATE_SHOWING = 3;
-    private static final int STATE_ANIMATING_OUT = 4;
-    private static final int STATE_GONE = 5;
+    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,
@@ -87,6 +91,9 @@
 
     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;
@@ -136,7 +143,8 @@
             return this;
         }
 
-        public AuthContainerView build(int modalityMask) { // TODO
+        public AuthContainerView build(int modalityMask) {
+            mConfig.mModalityMask = modalityMask;
             return new AuthContainerView(mConfig);
         }
     }
@@ -158,6 +166,9 @@
                 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);
             }
@@ -181,14 +192,36 @@
         mContainerView = (ViewGroup) factory.inflate(
                 R.layout.auth_container_view, this, false /* attachToRoot */);
 
-        // TODO: Depends on modality
-        mBiometricView = (AuthBiometricFaceView)
-                factory.inflate(R.layout.auth_biometric_face_view, null, false);
+        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);
 
-        mPanelView = mContainerView.findViewById(R.id.panel);
-        mPanelController = new AuthPanelController(mContext, mPanelView);
+        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);
@@ -281,13 +314,13 @@
         if (animate) {
             animateAway(false /* sendReason */, 0 /* reason */);
         } else {
-            mWindowManager.removeView(this);
+            removeWindowIfAttached();
         }
     }
 
     @Override
     public void dismissFromSystemServer() {
-        mWindowManager.removeView(this);
+        removeWindowIfAttached();
     }
 
     @Override
@@ -307,11 +340,12 @@
 
     @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);
     }
 
@@ -338,12 +372,15 @@
         }
         mContainerState = STATE_ANIMATING_OUT;
 
+        if (sendReason) {
+            mPendingCallbackReason = reason;
+        } else {
+            mPendingCallbackReason = null;
+        }
+
         final Runnable endActionRunnable = () -> {
             setVisibility(View.INVISIBLE);
-            mWindowManager.removeView(this);
-            if (sendReason) {
-                mConfig.mCallback.onDismissed(reason);
-            }
+            removeWindowIfAttached();
         };
 
         postOnAnimation(() -> {
@@ -369,6 +406,24 @@
         });
     }
 
+    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");
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ab89034..d10a3fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -29,7 +29,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.provider.Settings;
 import android.util.Log;
 import android.view.WindowManager;
 
@@ -46,8 +45,6 @@
  */
 public class AuthController extends SystemUI implements CommandQueue.Callbacks,
         AuthDialogCallback {
-    private static final String USE_NEW_DIALOG =
-            "com.android.systemui.biometrics.AuthController.USE_NEW_DIALOG";
 
     private static final String TAG = "BiometricPrompt/AuthController";
     private static final boolean DEBUG = true;
@@ -305,31 +302,24 @@
             mCurrentDialog.dismissWithoutCallback(false /* animate */);
             mCurrentDialog = null;
 
-            showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
+            // 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) {
-        if (Settings.Secure.getIntForUser(
-                mContext.getContentResolver(), USE_NEW_DIALOG, userId, 0) != 0) {
-            return new AuthContainerView.Builder(mContext)
-                    .setCallback(this)
-                    .setBiometricPromptBundle(biometricPromptBundle)
-                    .setRequireConfirmation(requireConfirmation)
-                    .setUserId(userId)
-                    .setOpPackageName(opPackageName)
-                    .setSkipIntro(skipIntro)
-                    .build(type);
-        } else {
-            return new BiometricDialogView.Builder(mContext)
-                    .setCallback(this)
-                    .setBiometricPromptBundle(biometricPromptBundle)
-                    .setRequireConfirmation(requireConfirmation)
-                    .setUserId(userId)
-                    .setOpPackageName(opPackageName)
-                    .setSkipIntro(skipIntro)
-                    .build(type);
-        }
+        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
index fb904231..edb2953 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -29,6 +29,9 @@
  * 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
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
deleted file mode 100644
index b985e1c..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ /dev/null
@@ -1,963 +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 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.Nullable;
-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.keyguard.WakefulnessLifecycle;
-import com.android.systemui.util.leak.RotationUtils;
-
-/**
- * Abstract base class. Shows a dialog for BiometricPrompt.
- */
-public abstract class BiometricDialogView extends LinearLayout implements AuthDialog {
-
-    private static final String TAG = "BiometricPrompt/DialogView";
-
-    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 AuthDialogCallback 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(AuthDialogCallback.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 AuthDialogCallback mCallback;
-        private Bundle mBundle;
-        private boolean mRequireConfirmation;
-        private int mUserId;
-        private String mOpPackageName;
-        private boolean mSkipIntro;
-
-        public Builder(Context context) {
-            mContext = context;
-        }
-
-        public Builder setCallback(AuthDialogCallback 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 Builder setSkipIntro(boolean skipIntro) {
-            mSkipIntro = skipIntro;
-            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);
-            dialog.setSkipIntro(mSkipIntro);
-            return dialog;
-        }
-    }
-
-    public static class Injector {
-        public WakefulnessLifecycle getWakefulnessLifecycle() {
-            return Dependency.get(WakefulnessLifecycle.class);
-        }
-    }
-
-    protected BiometricDialogView(Context context, AuthDialogCallback 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(AuthDialogCallback.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(AuthDialogCallback.DISMISSED_USER_CANCELED);
-            } else {
-                animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
-            }
-        });
-
-        mPositiveButton.setOnClickListener((View v) -> {
-            updateState(STATE_AUTHENTICATED);
-            mHandler.postDelayed(() -> {
-                animateAway(AuthDialogCallback.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(AuthDialogCallback.DISMISSED_USER_CANCELED);
-            }
-        });
-    }
-
-    private void animateAway(@AuthDialogCallback.DismissedReason int reason) {
-        animateAway(true /* sendReason */, reason);
-    }
-
-    /**
-     * Animate the dialog away
-     * @param reason one of the {@link AuthDialogCallback} codes
-     */
-    private void animateAway(boolean sendReason, @AuthDialogCallback.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, @Nullable Bundle savedState) {
-        if (savedState != null) {
-            restoreState(savedState);
-        }
-        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(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
-    }
-
-    @Override
-    public void onAuthenticationSucceeded() {
-        announceForAccessibility(getResources().getText(getAuthenticatedAccessibilityResourceId()));
-
-        if (requiresConfirmation()) {
-            updateState(STATE_PENDING_CONFIRMATION);
-        } else {
-            mHandler.postDelayed(() -> {
-                animateAway(AuthDialogCallback.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(AuthDialogCallback.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);
-    }
-
-    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/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
deleted file mode 100644
index d5dcbf1..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ /dev/null
@@ -1,252 +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.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;
-
-/**
- * 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 = "BiometricPrompt/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, AuthDialogCallback 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/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
deleted file mode 100644
index cda2176..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
+++ /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.
- */
-
-package com.android.systemui.biometrics;
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-/**
- * 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 = "BiometricPrompt/FingerprintDialogView";
-
-    protected FingerprintDialogView(Context context, AuthDialogCallback 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/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index edd8089..e00cf6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -16,8 +16,14 @@
 
 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) {
@@ -29,4 +35,15 @@
         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/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 94d9ede..8240345 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -48,12 +48,10 @@
 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.ZenModeConfig;
 import android.util.ArraySet;
@@ -61,8 +59,6 @@
 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;
 
@@ -76,6 +72,7 @@
 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;
@@ -129,11 +126,6 @@
     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
-
-    /** Flag to enable or disable the entire feature */
-    private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
-
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
     private final BubbleTaskStackListener mTaskStackListener;
@@ -511,7 +503,7 @@
 
     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, suppressFlyout);
@@ -560,7 +552,7 @@
 
                 // 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)
+                boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
                         || isClearAll || isUserDimiss || isSummaryCancel;
 
                 if (isSummaryOfBubbles) {
@@ -570,7 +562,7 @@
                 // The bubble notification sticks around in the data as long as the bubble is
                 // not dismissed and the app hasn't cancelled the notification.
                 Bubble bubble = mBubbleData.getBubbleWithKey(key);
-                boolean bubbleExtended = entry.isBubble() && userRemovedNotif;
+                boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
                 if (bubbleExtended) {
                     bubble.setShowInShadeWhenBubble(false);
                     bubble.setShowBubbleDot(false);
@@ -579,7 +571,7 @@
                     }
                     mNotificationEntryManager.updateNotifications();
                     return true;
-                } else if (!userRemovedNotif) {
+                } 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;
@@ -638,9 +630,6 @@
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
                 updateBubble(entry);
@@ -649,9 +638,6 @@
 
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry);
             if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) {
@@ -926,7 +912,9 @@
 
         @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);
             }
@@ -934,22 +922,17 @@
 
         @Override
         public void onSingleTaskDisplayEmpty(int displayId) {
-            final Bubble expandedBubble = getExpandedBubble(mContext);
-            if (expandedBubble == null) {
-                return;
-            }
-            if (expandedBubble.getDisplayId() == 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);
-                expandedBubble.getExpandedView().notifyDisplayEmpty();
             }
+            mBubbleData.notifyDisplayEmpty(displayId);
         }
     }
 
-    private static boolean areBubblesEnabled(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLES, 1) != 0;
-    }
-
     /**
      * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
      *
@@ -993,32 +976,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 81c8da8..d43e030 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -184,6 +184,8 @@
             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);
@@ -193,8 +195,10 @@
         } else {
             // Updates an existing bubble
             bubble.updateEntry(entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doUpdate(bubble);
         }
+
         if (bubble.shouldAutoExpand()) {
             setSelectedBubbleInternal(bubble);
             if (!mExpanded) {
@@ -392,6 +396,22 @@
         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);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index be10dc5..521ebde 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -105,6 +105,7 @@
     private Drawable mAppIcon;
 
     private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private WindowManager mWindowManager;
 
     private BubbleStackView mStackView;
 
@@ -202,9 +203,9 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         mPm = context.getPackageManager();
         mDisplaySize = new Point();
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
-        wm.getDefaultDisplay().getRealSize(mDisplaySize);
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
         Resources res = getResources();
         mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
@@ -326,7 +327,10 @@
         if (usingActivityView()) {
             int[] screenLoc = mActivityView.getLocationOnScreen();
             final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
-            final int keyboardTop = mDisplaySize.y - insets.getSystemWindowInsetBottom();
+            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));
         }
@@ -444,6 +448,7 @@
     }
 
     private int getMaxExpandedHeight() {
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
         int[] windowLocation = mActivityView.getLocationOnScreen();
         int bottomInset = getRootWindowInsets() != null
                 ? getRootWindowInsets().getStableInsetBottom()
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cc250b4..340dced 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -45,6 +45,7 @@
 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;
@@ -180,7 +181,9 @@
      */
     private float mVerticalPosPercentBeforeRotation = -1;
 
+    private int mMaxBubbles;
     private int mBubbleSize;
+    private int mBubbleElevation;
     private int mBubblePaddingTop;
     private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
@@ -325,7 +328,9 @@
         mInflater = LayoutInflater.from(context);
 
         Resources res = getResources();
+        mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
         mExpandedAnimateXDistance =
@@ -340,7 +345,8 @@
 
         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);
 
@@ -417,7 +423,35 @@
 
         mOrientationChangedListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    mExpandedAnimationController.updateOrientation(mOrientation);
+                    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(() -> {
@@ -488,6 +522,11 @@
     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(
@@ -1562,8 +1601,7 @@
         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);
+            bv.setZ((mMaxBubbles * mBubbleElevation) - i);
             // 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);
@@ -1584,11 +1622,9 @@
         int index = getBubbleIndex(expandedBubble);
         float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
         float halfBubble = mBubbleSize / 2f;
-
-        // 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;
+        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble;
+        // Padding might be adjusted for insets, so get it directly from the view
+        bubbleCenter -= mExpandedViewContainer.getPaddingLeft();
 
         expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
     }
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 c332d15..59d68bc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -21,6 +21,7 @@
 import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.view.DisplayCutout;
 import android.view.View;
 import android.view.WindowInsets;
 
@@ -57,6 +58,9 @@
     /** 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;
     /** Space between status bar and bubbles in the expanded state. */
@@ -69,8 +73,8 @@
     private Point mDisplaySize;
     /** Max number of bubbles shown in row above expanded view.*/
     private int mBubblesMaxRendered;
-    /** Width of current screen orientation. */
-    private float mScreenWidth;
+    /** What the current screen orientation is. */
+    private int mScreenOrientation;
 
     /** Whether the dragged-out bubble is in the dismiss target. */
     private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -97,8 +101,7 @@
 
     public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
             int orientation) {
-        mDisplaySize = displaySize;
-        updateOrientation(orientation);
+        updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
         mLauncherGridDiff = 30f;
     }
@@ -136,18 +139,16 @@
     /**
      * Update effective screen width based on current orientation.
      * @param orientation Landscape or portrait.
+     * @param displaySize Updated display size.
      */
-    public void updateOrientation(int orientation) {
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            mScreenWidth = mDisplaySize.y;
-        } else {
-            mScreenWidth = mDisplaySize.x;
-        }
+    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);
-            mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         }
     }
 
@@ -499,6 +500,50 @@
         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;
@@ -510,9 +555,12 @@
         final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
         final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        final float centerScreen = mScreenWidth / 2f;
+        // 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 = centerScreen - halfRow;
+        final float rowLeft = trueCenter - halfRow;
 
         return rowLeft;
     }
@@ -530,7 +578,7 @@
          *  Launcher's app icon grid edge that we must match
          */
         final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
-        final float maxRowWidth = mScreenWidth - rowMargins;
+        final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
 
         final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
         final float totalGapWidth = maxRowWidth - totalBubbleWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index c2b0fe4..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;
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index c9f5b79..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,8 +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;
 
@@ -51,6 +52,9 @@
 @Singleton
 public class FalsingManagerProxy implements FalsingManager {
 
+    private static final String PROXIMITY_SENSOR_TAG = "FalsingManager";
+
+    private final ProximitySensor mProximitySensor;
     private FalsingManager mInternalFalsingManager;
     private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
     private final DeviceConfigProxy mDeviceConfig;
@@ -58,7 +62,12 @@
 
     @Inject
     FalsingManagerProxy(Context context, PluginManager pluginManager,
-            @Named(MAIN_HANDLER_NAME) Handler handler, DeviceConfigProxy deviceConfig) {
+            @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());
@@ -113,8 +122,8 @@
         } else {
             mInternalFalsingManager = new BrightLineFalsingManager(
                     new FalsingDataProvider(context.getResources().getDisplayMetrics()),
-                    Dependency.get(AsyncSensorManager.class),
                     Dependency.get(KeyguardUpdateMonitor.class),
+                    mProximitySensor,
                     mDeviceConfig
             );
         }
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 9e646b62..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;
@@ -34,12 +30,12 @@
 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.
@@ -47,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;
@@ -59,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() {
@@ -88,12 +73,12 @@
 
     public BrightLineFalsingManager(
             FalsingDataProvider falsingDataProvider,
-            SensorManager sensorManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
+            ProximitySensor proximitySensor,
             DeviceConfigProxy deviceConfigProxy) {
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDataProvider = falsingDataProvider;
-        mSensorManager = sensorManager;
+        mProximitySensor = proximitySensor;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
 
         mMetricsLogger = new MetricsLogger();
@@ -111,24 +96,12 @@
     }
 
     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() {
@@ -170,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");
             }
@@ -190,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 9c03b91..520e0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DiagonalClassifier.java
@@ -25,6 +25,8 @@
 
 import com.android.systemui.util.DeviceConfigProxy;
 
+import java.util.Locale;
+
 /**
  * False on swipes that are too close to 45 degrees.
  *
@@ -84,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 0954f9e..7f45cc8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/DistanceClassifier.java
@@ -30,6 +30,7 @@
 import com.android.systemui.util.DeviceConfigProxy;
 
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Ensure that the swipe + momentum covers a minimum distance.
@@ -144,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;
@@ -166,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 1827047..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,12 +19,13 @@
 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;
 
 
 /**
@@ -99,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
@@ -124,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 a0da988..957ea8d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ZigZagClassifier.java
@@ -29,6 +29,7 @@
 
 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.
@@ -49,6 +50,10 @@
     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, DeviceConfigProxy deviceConfigProxy) {
         super(dataProvider);
@@ -139,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/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index c367286..4d3dc70 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,7 +33,8 @@
 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,13 +92,14 @@
                 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,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 026a625..a201400 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,14 @@
                         false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
         };
 
-        mProxSensor = new ProxSensor(policy);
-        mCallback = callback;
+        mProximitySensor = new ProximitySensor(context, sensorManager);
+
+        mProximitySensor.register(
+                proximityEvent -> {
+                    if (proximityEvent != null) {
+                        mProxCallback.accept(!proximityEvent.getNear());
+                    }
+                });
     }
 
     /**
@@ -236,7 +241,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 +280,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 bab64db..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,104 +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);
     }
 
-    /**
-     * @see DozeSensors.ProxSensor
-     */
-    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;
-        private boolean mUsingBrightnessSensor;
-
-        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));
-            mUsingBrightnessSensor = sensor != null;
-            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;
-        }
-
-        /**
-         * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent)
-         */
-        @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;
-                if (mUsingBrightnessSensor) {
-                    // The custom brightness sensor is gated by the proximity sensor and will
-                    // return 0 whenever prox is covered.
-                    isNear = event.values[0] == 0;
-                } else {
-                    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 c9c6a0c..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);
+                }
+            }
+        });
     }
 
     /**
@@ -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 6f8665a..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,7 +79,7 @@
         if (mGlobalActions == null) {
             mGlobalActions = new GlobalActionsDialog(mContext, manager);
         }
-        mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
+        mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPanelExtension.get());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
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 2960634..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,6 +108,9 @@
     }
 
     private boolean loadBitmap() {
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
+        }
         if (mWallpaperManager != null && mBitmap == null) {
             mBitmap = mWallpaperManager.getBitmap();
             mWallpaperManager.forgetLoadedWallpaper();
@@ -119,6 +123,9 @@
                 mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "loadBitmap done");
+        }
         return mBitmap != null;
     }
 
@@ -223,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/pip/BasePipManager.java b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
index e0fc31b..05be425 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/BasePipManager.java
@@ -27,5 +27,6 @@
     default void expandPip() {}
     default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
     void onConfigurationChanged(Configuration newConfig);
+    default void setShelfHeight(boolean visible, int height) {}
     default void dump(PrintWriter pw) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 30be775..686e7db 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -18,6 +18,7 @@
 
 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;
@@ -46,9 +47,6 @@
     private static final String TAG = PipBoundsHandler.class.getSimpleName();
     private static final float INVALID_SNAP_FRACTION = -1f;
 
-    // System.identityHashCode guarantees zero for null object.
-    private static final int INVALID_SYSTEM_IDENTITY_TOKEN = 0;
-
     private final Context mContext;
     private final IWindowManager mWindowManager;
     private final PipSnapAlgorithm mSnapAlgorithm;
@@ -57,8 +55,14 @@
     private final Rect mTmpInsets = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
+    /**
+     * Tracks the destination bounds, used for any following
+     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} calculations.
+     */
+    private final Rect mLastDestinationBounds = new Rect();
+
     private IPinnedStackController mPinnedStackController;
-    private int mLastPipToken;
+    private ComponentName mLastPipComponentName;
     private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
 
     private float mDefaultAspectRatio;
@@ -80,8 +84,11 @@
         mContext = context;
         mSnapAlgorithm = new PipSnapAlgorithm(context);
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
-        mAspectRatio = mDefaultAspectRatio;
         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;
     }
 
     /**
@@ -119,6 +126,21 @@
     }
 
     /**
+     * Sets both shelf visibility and its height if applicable.
+     * @return {@code true} if the internal shelf state is changed, {@code false} otherwise.
+     */
+    public boolean setShelfHeight(boolean shelfVisible, int shelfHeight) {
+        final boolean shelfShowing = shelfVisible && shelfHeight > 0;
+        if (shelfShowing == mIsShelfShowing && shelfHeight == mShelfHeight) {
+            return false;
+        }
+
+        mIsShelfShowing = shelfVisible;
+        mShelfHeight = shelfHeight;
+        return true;
+    }
+
+    /**
      * Responds to IPinnedStackListener on IME visibility change.
      */
     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -127,14 +149,6 @@
     }
 
     /**
-     * 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) {
@@ -161,27 +175,31 @@
     }
 
     /**
-     * Responds to IPinnedStackListener on saving reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on saving reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onSaveReentrySnapFraction(int token, Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipToken = token;
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        mReentrySnapFraction = getSnapFraction(bounds);
+        mLastPipComponentName = componentName;
     }
 
     /**
-     * Responds to IPinnedStackListener on resetting reentry snap fraction for a given token.
-     * Token should be generated via {@link System#identityHashCode(Object)}
+     * Responds to IPinnedStackListener on resetting reentry snap fraction
+     * for a given {@link ComponentName}.
      */
-    public void onResetReentrySnapFraction(int token) {
-        if (mLastPipToken == token) {
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        if (componentName.equals(mLastPipComponentName)) {
             onResetReentrySnapFractionUnchecked();
         }
     }
 
     private void onResetReentrySnapFractionUnchecked() {
         mReentrySnapFraction = INVALID_SNAP_FRACTION;
-        mLastPipToken = INVALID_SYSTEM_IDENTITY_TOKEN;
+        mLastPipComponentName = null;
+    }
+
+    public Rect getLastDestinationBounds() {
+        return mLastDestinationBounds;
     }
 
     /**
@@ -212,24 +230,29 @@
     /**
      * Responds to IPinnedStackListener on preparing the pinned stack animation.
      */
-    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
-        final Rect targetStackBounds;
-        if (stackBounds == null) {
-            targetStackBounds = getDefaultBounds(mReentrySnapFraction);
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        final Rect destinationBounds;
+        if (bounds == null) {
+            destinationBounds = getDefaultBounds(mReentrySnapFraction);
         } else {
-            targetStackBounds = new Rect();
-            targetStackBounds.set(stackBounds);
+            destinationBounds = new Rect(bounds);
         }
         if (isValidPictureInPictureAspectRatio(aspectRatio)) {
-            transformBoundsToAspectRatio(targetStackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio,
+                    false /* useCurrentMinEdgeSize */);
         }
-        if (targetStackBounds.equals(stackBounds)) {
+        if (destinationBounds.equals(bounds)) {
             return;
         }
         mAspectRatio = aspectRatio;
         onResetReentrySnapFractionUnchecked();
-        // TODO: callback Window Manager on starting animation with calculated bounds
+        try {
+            mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
+                    -1 /* animationDuration */);
+            mLastDestinationBounds.set(destinationBounds);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start PiP animation from SysUI", e);
+        }
     }
 
     /**
@@ -358,6 +381,7 @@
     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);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 37c8163..682c76c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -85,6 +85,14 @@
         mPipManager.onConfigurationChanged(newConfig);
     }
 
+    public void setShelfHeight(boolean visible, int height) {
+        if (mPipManager == null) {
+            return;
+        }
+
+        mPipManager.setShelfHeight(visible, height);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mPipManager == null) {
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 3be3422..369073c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -32,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;
 
@@ -58,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;
@@ -119,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);
             });
         }
@@ -131,38 +137,60 @@
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
                 mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
 
         @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-            mHandler.post(() -> {
-               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) {
-            mHandler.post(() -> {
-                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
-                        fromImeAdjustment, fromShelfAdjustment, displayRotation);
-            });
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
+            mHandler.post(() -> updateMovementBounds(animatingBounds, fromImeAdjustment,
+                    fromShelfAdjustment));
         }
 
         @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);
             });
         }
     }
@@ -184,12 +212,13 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
         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());
 
@@ -237,6 +266,31 @@
     }
 
     /**
+     * Sets both shelf visibility and its height.
+     */
+    @Override
+    public void setShelfHeight(boolean visible, int height) {
+        mHandler.post(() -> {
+            final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height);
+            if (changed) {
+                mTouchHandler.onShelfVisibilityChanged(visible, height);
+                updateMovementBounds(mPipBoundsHandler.getLastDestinationBounds(),
+                        false /* fromImeAdjustment */, true /* fromShelfAdjustment */);
+            }
+        });
+    }
+
+    private void updateMovementBounds(Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment) {
+        // Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+        mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                animatingBounds, mTmpDisplayInfo);
+        mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+                mTmpDisplayInfo.rotation);
+    }
+
+    /**
      * Gets an instance of {@link PipManager}.
      */
     public static PipManager getInstance() {
@@ -252,5 +306,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/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 30cf412..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) {
@@ -787,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/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 918af4f..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);
         }
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..b9f3a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -16,17 +16,25 @@
 
 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.Dependency.MAIN_LOOPER;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
+import android.annotation.MainThread;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 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;
 
@@ -36,6 +44,8 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 
+import java.util.function.Consumer;
+
 import javax.inject.Inject;
 import javax.inject.Named;
 
@@ -43,7 +53,6 @@
  * Displays Carrier name and network status in QS
  */
 public class QSCarrierGroup extends LinearLayout implements
-        CarrierTextController.CarrierTextCallback,
         NetworkController.SignalCallback, View.OnClickListener {
 
     private static final String TAG = "QSCarrierGroup";
@@ -52,28 +61,44 @@
      */
     private static final int SIM_SLOTS = 3;
     private final NetworkController mNetworkController;
+    private final Handler mBgHandler;
+    private final H mMainHandler;
 
     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 CarrierTextController.CarrierTextCallback mCallback;
     private ActivityStarter mActivityStarter;
 
     private boolean mListening;
 
     @Inject
     public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            NetworkController networkController, ActivityStarter activityStarter) {
+            NetworkController networkController, ActivityStarter activityStarter,
+            @Named(BG_HANDLER_NAME) Handler handler,
+            @Named(MAIN_LOOPER_NAME) Looper looper) {
         super(context, attrs);
         mNetworkController = networkController;
         mActivityStarter = activityStarter;
+        mBgHandler = handler;
+        mMainHandler = new H(looper, this::handleUpdateCarrierInfo, this::handleUpdateState);
+        mCallback = new Callback(mMainHandler);
+    }
+
+    @VisibleForTesting
+    protected CarrierTextController.CarrierTextCallback getCallback() {
+        return mCallback;
     }
 
     @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),
+                Dependency.get(MAIN_LOOPER));
     }
 
     @Override
@@ -94,10 +119,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 +138,7 @@
             return;
         }
         mListening = listening;
-        // TODO(b/140053526)
-        whitelistIpcs(this::updateListeners);
+        mBgHandler.post(this::updateListeners);
     }
 
     @Override
@@ -126,14 +153,20 @@
             if (mNetworkController.hasVoiceCallingFeature()) {
                 mNetworkController.addCallback(this);
             }
-            mCarrierTextController.setListening(this);
+            mCarrierTextController.setListening(mCallback);
         } else {
             mNetworkController.removeCallback(this);
             mCarrierTextController.setListening(null);
         }
     }
 
+    @MainThread
     private void handleUpdateState() {
+        if (!mMainHandler.getLooper().isCurrentThread()) {
+            mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
+            return;
+        }
+
         for (int i = 0; i < SIM_SLOTS; i++) {
             mCarrierGroups[i].updateState(mInfos[i]);
         }
@@ -153,54 +186,56 @@
         return SubscriptionManager.getSlotIndex(subscriptionId);
     }
 
-    @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);
+    @MainThread
+    private void handleUpdateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+        if (!mMainHandler.getLooper().isCurrentThread()) {
+            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
+            return;
+        }
+
+        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();
+        handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
     }
 
     @Override
@@ -223,7 +258,7 @@
         mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
         mInfos[slotIndex].typeContentDescription = typeContentDescription;
         mInfos[slotIndex].roaming = roaming;
-        handleUpdateState();
+        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
     }
 
     @Override
@@ -233,7 +268,7 @@
                 mInfos[i].visible = false;
             }
         }
-        handleUpdateState();
+        mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
     }
 
     static final class CellSignalState {
@@ -243,4 +278,47 @@
         String typeContentDescription;
         boolean roaming;
     }
+
+    private static class H extends Handler {
+        private Consumer<CarrierTextController.CarrierTextCallbackInfo> mUpdateCarrierInfo;
+        private Runnable mUpdateState;
+        static final int MSG_UPDATE_CARRIER_INFO = 0;
+        static final int MSG_UPDATE_STATE = 1;
+
+        H(Looper looper,
+                Consumer<CarrierTextController.CarrierTextCallbackInfo> updateCarrierInfo,
+                Runnable updateState) {
+            super(looper);
+            mUpdateCarrierInfo = updateCarrierInfo;
+            mUpdateState = updateState;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_CARRIER_INFO:
+                    mUpdateCarrierInfo.accept(
+                            (CarrierTextController.CarrierTextCallbackInfo) msg.obj);
+                    break;
+                case MSG_UPDATE_STATE:
+                    mUpdateState.run();
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    }
+
+    private static class Callback implements CarrierTextController.CarrierTextCallback {
+        private H mMainHandler;
+
+        Callback(H handler) {
+            mMainHandler = handler;
+        }
+
+        @Override
+        public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
+            mMainHandler.obtainMessage(H.MSG_UPDATE_CARRIER_INFO, info).sendToTarget();
+        }
+    }
 }
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 1211e13..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 */);
@@ -155,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 38153ec..7fb5207 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -46,8 +46,8 @@
 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;
@@ -68,7 +68,7 @@
 
     private final QSDetailClipper mClipper;
     private final LightBarController mLightBarController;
-    private KeyguardMonitor mKeyguardMonitor;
+    private KeyguardStateController mKeyguardStateController;
     private final ScreenLifecycle mScreenLifecycle;
     private final TileQueryHelper mTileQueryHelper;
     private final View mTransparentView;
@@ -89,7 +89,7 @@
     @Inject
     public QSCustomizer(Context context, AttributeSet attrs,
             LightBarController lightBarController,
-            KeyguardMonitor keyguardMonitor,
+            KeyguardStateController keyguardStateController,
             ScreenLifecycle screenLifecycle) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
 
@@ -124,7 +124,7 @@
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
         mLightBarController = lightBarController;
-        mKeyguardMonitor = keyguardMonitor;
+        mKeyguardStateController = keyguardStateController;
         mScreenLifecycle = screenLifecycle;
         updateNavBackDrop(getResources().getConfiguration());
     }
@@ -187,7 +187,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(true);
             mNotifQsContainer.setCustomizerShowing(true);
-            mKeyguardMonitor.addCallback(mKeyguardCallback);
+            mKeyguardStateController.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -203,7 +203,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(false);
             mNotifQsContainer.setCustomizerShowing(true);
-            mKeyguardMonitor.addCallback(mKeyguardCallback);
+            mKeyguardStateController.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -227,7 +227,7 @@
             }
             mNotifQsContainer.setCustomizerAnimating(animate);
             mNotifQsContainer.setCustomizerShowing(false);
-            mKeyguardMonitor.removeCallback(mKeyguardCallback);
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -283,7 +283,7 @@
 
     public void saveInstanceState(Bundle outState) {
         if (isShown) {
-            mKeyguardMonitor.removeCallback(mKeyguardCallback);
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
         }
         outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
     }
@@ -315,7 +315,7 @@
         @Override
         public void onKeyguardShowingChanged() {
             if (!isAttachedToWindow()) return;
-            if (mKeyguardMonitor.isShowing() && !mOpening) {
+            if (mKeyguardStateController.isShowing() && !mOpening) {
                 hide();
             }
         }
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 3d6ee4c..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;
@@ -98,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;
@@ -132,6 +134,8 @@
     protected QSTileImpl(QSHost host) {
         mHost = host;
         mContext = host.getContext();
+        mState = newTileState();
+        mTmpState = newTileState();
         mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
     }
 
@@ -422,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,
@@ -429,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;
@@ -614,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/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index b79b662..b7ce101 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -118,6 +118,11 @@
     }
 
     @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);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 9268ee0..e0ae8ed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -58,6 +58,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.pip.PipUI;
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -353,6 +354,20 @@
             }
         }
 
+        @Override
+        public void setShelfHeight(boolean visible, int shelfHeight) {
+            if (!verifyCaller("setShelfHeight")) {
+                return;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                final PipUI component = SysUiServiceProvider.getComponent(mContext, PipUI.class);
+                component.setShelfHeight(visible, shelfHeight);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 2ae2ac5..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,7 +137,7 @@
                 WakeLock.createPartial(context, "Doze:KeyguardIndication"),
                 Dependency.get(ShadeController.class),
                 Dependency.get(AccessibilityController.class),
-                UnlockMethodCache.getInstance(context),
+                Dependency.get(KeyguardStateController.class),
                 Dependency.get(StatusBarStateController.class),
                 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);
@@ -580,7 +555,6 @@
         }
         mDozing = dozing;
         updateIndication(false);
-        updateDisclosure();
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -610,7 +584,7 @@
     }
 
     @Override
-    public void onUnlockMethodStateChanged() {
+    public void onUnlockedChanged() {
         updateIndication(!mDozing);
     }
 
@@ -640,13 +614,6 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            if (showing) {
-                updateDisclosure();
-            }
-        }
-
-        @Override
         public void onBiometricHelp(int msgId, String helpString,
                 BiometricSourceType biometricSourceType) {
             KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
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/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 1400921..6c0f90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_CHILD_NOTIFICATIONS;
 
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.NotificationManager;
 import android.content.ComponentName;
@@ -57,8 +58,9 @@
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
 
-    private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
     private final Context mContext;
+    private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
+    @Nullable private NotifServiceListener mDownstreamListener;
 
     @Inject
     public NotificationListener(Context context) {
@@ -69,6 +71,10 @@
         mSettingsListeners.add(listener);
     }
 
+    public void setDownstreamListener(NotifServiceListener downstreamListener) {
+        mDownstreamListener = downstreamListener;
+    }
+
     @Override
     public void onListenerConnected() {
         if (DEBUG) Log.d(TAG, "onListenerConnected");
@@ -81,6 +87,9 @@
         final RankingMap currentRanking = getCurrentRanking();
         Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
             for (StatusBarNotification sbn : notifications) {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationPosted(sbn, currentRanking);
+                }
                 mEntryManager.addNotification(sbn, currentRanking);
             }
         });
@@ -95,6 +104,11 @@
         if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
                 processForRemoteInput(sbn.getNotification(), mContext);
+
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationPosted(sbn, rankingMap);
+                }
+
                 String key = sbn.getKey();
                 boolean isUpdate =
                         mEntryManager.getNotificationData().get(key) != null;
@@ -133,6 +147,9 @@
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
             final String key = sbn.getKey();
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationRemoved(sbn, rankingMap, reason);
+                }
                 mEntryManager.removeNotification(key, rankingMap, reason);
             });
         }
@@ -149,6 +166,9 @@
         if (rankingMap != null) {
             RankingMap r = onPluginRankingUpdate(rankingMap);
             Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
+                if (mDownstreamListener != null) {
+                    mDownstreamListener.onNotificationRankingUpdate(rankingMap);
+                }
                 mEntryManager.updateNotificationRanking(r);
             });
         }
@@ -175,4 +195,12 @@
 
         default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
     }
+
+    /** Interface for listening to add/remove events that we receive from NotificationManager. */
+    public interface NotifServiceListener {
+        void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap);
+        void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap);
+        void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason);
+        void onNotificationRankingUpdate(RankingMap rankingMap);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 289277e..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;
@@ -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 66a0619..50d9bae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -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/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/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java
new file mode 100644
index 0000000..df70828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.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 com.android.systemui.statusbar.notification;
+
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.systemui.statusbar.NotificationListener;
+
+import javax.inject.Inject;
+
+/**
+ * Initialization code for the new notification pipeline.
+ */
+public class NotifPipelineInitializer {
+
+    @Inject
+    public NotifPipelineInitializer() {
+    }
+
+    public void initialize(
+            NotificationListener notificationService) {
+
+        // TODO Put real code here
+        notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() {
+            @Override
+            public void onNotificationPosted(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationPosted " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn,
+                    NotificationListenerService.RankingMap rankingMap, int reason) {
+                Log.d(TAG, "onNotificationRemoved " + sbn.getKey());
+            }
+
+            @Override
+            public void onNotificationRankingUpdate(
+                    NotificationListenerService.RankingMap rankingMap) {
+                Log.d(TAG, "onNotificationRankingUpdate");
+            }
+        });
+    }
+
+    private static final String TAG = "NotifInitializer";
+}
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 0032174..90301c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -22,6 +22,7 @@
 import android.app.Notification;
 import android.content.Context;
 import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -124,7 +125,7 @@
 
     @Inject
     public NotificationEntryManager(Context context) {
-        mNotificationData = new NotificationData();
+        mNotificationData = new NotificationData(context);
     }
 
     /** Adds a {@link NotificationEntryListener}. */
@@ -281,11 +282,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
@@ -376,7 +391,7 @@
         }
 
         mNotificationData.updateRanking(rankingMap);
-        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+        Ranking ranking = new Ranking();
         rankingMap.getRanking(key, ranking);
 
         NotificationEntry entry = new NotificationEntry(notification, ranking);
@@ -471,7 +486,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 +514,11 @@
         if (rankingMap == null) {
             return;
         }
-        NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
         for (NotificationEntry pendingNotification : mPendingNotifications.values()) {
-            rankingMap.getRanking(pendingNotification.key, ranking);
-            pendingNotification.setRanking(ranking);
+            Ranking ranking = new Ranking();
+            if (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 f99662e..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;
         }
 
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 150667b..9362d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -169,7 +169,7 @@
             return false;
         }
 
-        if (!entry.canBubble) {
+        if (!entry.canBubble()) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
             }
@@ -244,7 +244,7 @@
             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());
             }
@@ -307,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());
             }
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 727e245..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
@@ -317,14 +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 List<SnoozeCriterion> getSnoozeCriteria(String key) {
         if (mRankingMap != null) {
             getRanking(key, mTmpRanking);
@@ -349,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);
@@ -414,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);
         }
     }
 
@@ -466,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 6f5fe8b..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,6 +21,7 @@
 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;
@@ -29,6 +30,8 @@
 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;
@@ -59,9 +62,9 @@
 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,14 +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;
     private Ranking mRanking;
 
-    public long lastAudiblyAlertedMs;
     public boolean noisy;
-    public boolean ambient;
-    public int importance;
     public StatusBarIconView icon;
     public StatusBarIconView expandedIcon;
     public StatusBarIconView centeredIcon;
@@ -103,14 +104,7 @@
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
-    public List<SnoozeCriterion> snoozeCriteria;
-    public int userSentiment = 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
@@ -119,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
 
@@ -151,10 +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;
+    public boolean isVisuallyInterruptive;
 
     /**
      * Whether this notification is shown to the user as a high priority notification: visible on
@@ -162,44 +152,18 @@
      */
     private boolean mHighPriority;
 
-    private boolean mIsTopBucket;
-
     private boolean mSensitive = true;
     private Runnable mOnSensitiveChangedListener;
     private boolean mAutoHeadsUp;
     private boolean mPulseSupressed;
+    private int mBucket = BUCKET_ALERTING;
 
     public NotificationEntry(
-            StatusBarNotification sbn,
+            @NonNull StatusBarNotification sbn,
             @NonNull Ranking ranking) {
-        this(sbn, ranking, false);
-    }
-
-    private NotificationEntry(
-            StatusBarNotification sbn,
-            Ranking ranking,
-            boolean isTest) {
         this.key = sbn.getKey();
-        this.notification = sbn;
-
-        // TODO: Update tests to no longer need to pass null ranking
-        if (ranking != null) {
-            setRanking(ranking);
-        } else if (!isTest) {
-            throw new IllegalArgumentException("Ranking cannot be null");
-        }
-    }
-
-    /**
-     * Method for old tests that build NotificationEntries with a ranking.
-     *
-     * @deprecated New tests should pass a ranking object as well.
-     */
-    @VisibleForTesting
-    @Deprecated
-    public static NotificationEntry buildForTest(StatusBarNotification sbn) {
-        // TODO START here this will NPE on all tests
-        return new NotificationEntry(sbn, null, true);
+        setNotification(sbn);
+        setRanking(ranking);
     }
 
     /** The key for this notification. Guaranteed to be immutable and unique */
@@ -220,11 +184,12 @@
      * TODO: Make this package-private
      */
     public void setNotification(StatusBarNotification sbn) {
-        if (!sbn.getKey().equals(key)) {
+        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();
     }
 
     /**
@@ -241,27 +206,61 @@
      * 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;
-
-        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();
+        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;
     }
@@ -278,22 +277,41 @@
         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;
     }
 
+    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());
+                }
+            }
+        }
+    }
+
+    boolean hasAssociatedPeople() {
+        return mAssociatedPeople.size() > 0;
+    }
+
     /**
      * Returns the data needed for a bubble for this notification, if it exists.
      */
@@ -310,6 +328,15 @@
         }
     }
 
+    @NotificationSectionsManager.PriorityBucket
+    public int getBucket() {
+        return mBucket;
+    }
+
+    public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+        mBucket = bucket;
+    }
+
     public ExpandableNotificationRow getRow() {
         return row;
     }
@@ -788,7 +815,7 @@
         if (isExemptFromDndVisualSuppression()) {
             return false;
         }
-        return (suppressedVisualEffects & effect) != 0;
+        return (getSuppressedVisualEffects() & effect) != 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/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 3e8825d..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 */);
         });
@@ -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 b6f82a1..68eca8d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -80,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;
@@ -89,7 +90,7 @@
  * text.
  */
 public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
-        UnlockMethodCache.OnUnlockMethodChangedListener,
+        KeyguardStateController.Callback,
         AccessibilityController.AccessibilityStateChangedCallback {
 
     final static String TAG = "StatusBar/KeyguardBottomAreaView";
@@ -116,11 +117,9 @@
     private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
     private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
 
-    private final UnlockMethodCache mUnlockMethodCache;
     private KeyguardAffordanceView mRightAffordanceView;
     private KeyguardAffordanceView mLeftAffordanceView;
     private ViewGroup mIndicationArea;
-    private TextView mEnterpriseDisclosure;
     private TextView mIndicationText;
     private ViewGroup mPreviewContainer;
     private ViewGroup mOverlayContainer;
@@ -129,6 +128,7 @@
     private View mCameraPreview;
 
     private ActivityStarter mActivityStarter;
+    private KeyguardStateController mKeyguardStateController;
     private LockPatternUtils mLockPatternUtils;
     private FlashlightController mFlashlightController;
     private PreviewInflater mPreviewInflater;
@@ -184,7 +184,6 @@
     public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
     }
 
     private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@@ -234,14 +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();
+        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+        mKeyguardStateController.addCallback(this);
         setClipChildren(false);
         setClipToPadding(false);
         inflateCameraPreview();
@@ -279,13 +278,13 @@
         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
                 UserHandle.ALL, filter, null, null);
         Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
-        mUnlockMethodCache.addListener(this);
+        mKeyguardStateController.addCallback(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        mUnlockMethodCache.removeListener(this);
+        mKeyguardStateController.removeCallback(this);
         mAccessibilityController.removeStateChangedCallback(this);
         mRightExtension.destroy();
         mLeftExtension.destroy();
@@ -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));
@@ -550,7 +546,7 @@
                 mAssistManager.launchVoiceAssistFromKeyguard();
             }
         };
-        if (mStatusBar.isKeyguardCurrentlySecure()) {
+        if (!mKeyguardStateController.canDismissLockScreen()) {
             AsyncTask.execute(runnable);
         } else {
             boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
@@ -615,7 +611,7 @@
     }
 
     @Override
-    public void onUnlockMethodStateChanged() {
+    public void onUnlockedChanged() {
         updateCameraVisibility();
     }
 
@@ -817,11 +813,9 @@
 
         @Override
         public Intent getIntent() {
-            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-            boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
-                    KeyguardUpdateMonitor.getCurrentUser());
-            boolean secure = mUnlockMethodCache.isMethodSecure();
-            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 5001006..c3de843 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -46,6 +46,7 @@
 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 f7bb97b..d9de59e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -24,7 +24,7 @@
 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(
     private val statusBarStateController: StatusBarStateController,
@@ -66,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 62d855d..4927ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -53,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;
 
@@ -68,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;
@@ -78,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;
@@ -105,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;
@@ -125,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() {
@@ -177,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 = Dependency.get(KeyguardUpdateMonitor.class);
         mAccessibilityController = accessibilityController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
         mBypassController = bypassController;
         mWakeUpCoordinator = wakeUpCoordinator;
-        mKeyguardMonitor = keyguardMonitor;
+        mKeyguardStateController = keyguardStateController;
         mDockManager = dockManager;
         mHeadsUpManager = headsUpManager;
     }
@@ -199,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) {
@@ -217,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);
         }
@@ -366,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);
@@ -464,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 {}
@@ -511,8 +523,8 @@
 
     private int getState() {
         KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-        if ((mUnlockMethodCache.canSkipBouncer() || !mKeyguardShowing
-                || mKeyguardMonitor.isKeyguardGoingAway()) && !mSimLocked) {
+        if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
+                || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
             return STATE_LOCK_OPEN;
         } else if (mTransientBiometricsError) {
             return STATE_BIOMETRICS_ERROR;
@@ -570,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.
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/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 a79ecd9..86da10a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -102,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;
@@ -177,12 +178,22 @@
     @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) {
+                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+                            && !mDelayShowingKeyguardStatusBar) {
                         mFirstBypassAttempt = false;
                         animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
                     }
@@ -191,6 +202,17 @@
                 @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;
+                    }
                 }
             };
 
@@ -413,7 +435,17 @@
     private boolean mShowingKeyguardHeadsUp;
     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;
@@ -450,6 +482,7 @@
         mKeyguardBypassController = bypassController;
         mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
         dynamicPrivacyController.addListener(this);
 
         mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -1650,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);
@@ -1668,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) {
@@ -1745,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;
         }
@@ -1798,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();
@@ -1827,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
@@ -2391,7 +2424,8 @@
                 * mKeyguardStatusBarAnimateAlpha;
         newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
         mKeyguardStatusBar.setAlpha(newAlpha);
-        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace();
+        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+                || mDelayShowingKeyguardStatusBar;
         mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
                 ? VISIBLE : INVISIBLE);
     }
@@ -2522,7 +2556,7 @@
 
     @Override
     protected void onTrackingStarted() {
-        mFalsingManager.onTrackingStarted(mStatusBar.isKeyguardCurrentlySecure());
+        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
         super.onTrackingStarted();
         if (mQsFullyExpanded) {
             mQsExpandImmediate = true;
@@ -2812,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 8c95b84..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;
@@ -129,7 +129,7 @@
     protected final ScrimView mScrimBehind;
     protected final ScrimView mScrimForBubble;
 
-    private final UnlockMethodCache mUnlockMethodCache;
+    private final KeyguardStateController mKeyguardStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
     private final AlarmTimeout mTimeTicker;
@@ -187,7 +187,7 @@
     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;
@@ -196,8 +196,8 @@
         mScrimVisibleListener = scrimVisibleListener;
 
         mContext = scrimBehind.getContext();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
-        mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
+        mKeyguardStateController = keyguardStateController;
+        mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
         mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
@@ -210,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());
             }
         });
 
@@ -367,7 +367,7 @@
 
     public void onTrackingStarted() {
         mTracking = true;
-        mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
+        mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
     }
 
     public void onExpandingFinished() {
@@ -473,11 +473,11 @@
             if (mDarkenWhileDragging) {
                 mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
                         interpolatedFract);
-                mInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             } else {
                 mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
                         interpolatedFract);
-                mInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             }
             mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     mState.getBehindTint(), interpolatedFract);
@@ -503,13 +503,14 @@
      * device is dozing when the light sensor is on.
      */
     public void setAodFrontScrimAlpha(float alpha) {
-        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
-                && mInFrontAlpha != alpha) {
+        if (((mState == ScrimState.AOD && mDozeParameters.getAlwaysOn())
+                || mState == ScrimState.PULSING) && mInFrontAlpha != alpha) {
             mInFrontAlpha = alpha;
             updateScrims();
         }
 
         mState.AOD.setAodFrontScrimAlpha(alpha);
+        mState.PULSING.setAodFrontScrimAlpha(alpha);
     }
 
     /**
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 c9acbad..7463c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -139,7 +139,7 @@
     PULSING(5) {
         @Override
         public void prepare(ScrimState previousState) {
-            mFrontAlpha = 0f;
+            mFrontAlpha = mAodFrontScrimAlpha;
             mBubbleAlpha = 0f;
             mBehindTint = Color.BLACK;
             mFrontTint = Color.BLACK;
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 6b12c61..377d138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -197,6 +197,7 @@
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotifPipelineInitializer;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationAlertingManager;
 import com.android.systemui.statusbar.notification.NotificationClicker;
@@ -212,7 +213,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 +222,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 +247,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 +359,6 @@
     protected PhoneStatusBarView mStatusBarView;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected StatusBarWindowController mStatusBarWindowController;
-    protected UnlockMethodCache mUnlockMethodCache;
     @VisibleForTesting
     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @VisibleForTesting
@@ -378,6 +378,8 @@
     @Inject
     KeyguardBypassController mKeyguardBypassController;
     @Inject
+    KeyguardStateController mKeyguardStateController;
+    @Inject
     protected HeadsUpManagerPhone mHeadsUpManager;
     @Inject
     DynamicPrivacyController mDynamicPrivacyController;
@@ -389,6 +391,8 @@
     @Inject
     @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME)
     boolean mAllowNotificationLongPress;
+    @Inject
+    protected NotifPipelineInitializer mNotifPipelineInitializer;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -545,7 +549,7 @@
                         + "mStatusBarKeyguardViewManager was null");
                 return;
             }
-            if (mKeyguardMonitor.isKeyguardFadingAway()) {
+            if (mKeyguardStateController.isKeyguardFadingAway()) {
                 mStatusBarKeyguardViewManager.onKeyguardFadedAway();
             }
         }
@@ -559,7 +563,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;
@@ -786,8 +789,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);
@@ -959,7 +961,7 @@
                     }
                 }, DozeParameters.getInstance(mContext),
                 mContext.getSystemService(AlarmManager.class),
-                mKeyguardMonitor);
+                mKeyguardStateController);
         mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
                 mHeadsUpManager, mNotificationIconAreaController, mScrimController);
         mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context));
@@ -1116,7 +1118,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);
@@ -1129,6 +1131,8 @@
 
         mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
         mNotificationListController.bind();
+
+        mNotifPipelineInitializer.initialize(mNotificationListener);
     }
 
     /**
@@ -1257,8 +1261,8 @@
         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,
@@ -1374,7 +1378,7 @@
      * Asks {@link KeyguardUpdateMonitor} to run face auth.
      */
     public void requestFaceAuth() {
-        if (!mUnlockMethodCache.canSkipBouncer()) {
+        if (!mKeyguardStateController.canDismissLockScreen()) {
             mKeyguardUpdateMonitor.requestFaceAuth();
         }
     }
@@ -1558,9 +1562,8 @@
         logStateToEventlog();
     }
 
-    @Override  // UnlockMethodCache.OnUnlockMethodChangedListener
-    public void onUnlockMethodStateChanged() {
-        // Unlock method state changed. Notify KeguardMonitor
+    @Override
+    public void onUnlockedChanged() {
         updateKeyguardState();
         logStateToEventlog();
     }
@@ -1623,10 +1626,6 @@
         }
     }
 
-    public boolean isKeyguardCurrentlySecure() {
-        return !mUnlockMethodCache.canSkipBouncer();
-    }
-
     public void setPanelExpanded(boolean isExpanded) {
         mPanelExpanded = isExpanded;
         updateHideIconsForBouncer(false /* animate */);
@@ -1818,8 +1817,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;
         }
 
@@ -2428,10 +2427,6 @@
             mLightBarController.dump(fd, pw, args);
         }
 
-        if (mUnlockMethodCache != null) {
-            mUnlockMethodCache.dump(pw);
-        }
-
         if (mKeyguardBypassController != null) {
             mKeyguardBypassController.dump(pw);
         }
@@ -2684,7 +2679,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.
@@ -2827,14 +2822,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);
@@ -2848,7 +2843,7 @@
                     isOccluded ? 1 : 0,
                     isBouncerShowing ? 1 : 0,
                     isSecure ? 1 : 0,
-                    canSkipBouncer ? 1 : 0);
+                    unlocked ? 1 : 0);
             mLastLoggedStateFingerprint = stateFingerprint;
         }
     }
@@ -3048,7 +3043,7 @@
 
     public void showKeyguardImpl() {
         mIsKeyguard = true;
-        if (mKeyguardMonitor.isLaunchTransitionFadingAway()) {
+        if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
             mNotificationPanel.animate().cancel();
             onLaunchTransitionFadingEnded();
         }
@@ -3080,7 +3075,7 @@
         mNotificationPanel.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
-        mKeyguardMonitor.setLaunchTransitionFadingAway(false);
+        mKeyguardStateController.setLaunchTransitionFadingAway(false);
         mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
@@ -3105,7 +3100,7 @@
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
         Runnable hideRunnable = () -> {
-            mKeyguardMonitor.setLaunchTransitionFadingAway(true);
+            mKeyguardStateController.setLaunchTransitionFadingAway(true);
             if (beforeFading != null) {
                 beforeFading.run();
             }
@@ -3198,7 +3193,7 @@
             if (!mStatusBarStateController.isKeyguardRequested()) {
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             }
-            long delay = mKeyguardMonitor.calculateGoingToFullShadeDelay();
+            long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
             mNotificationPanel.animateToFullShade(delay);
             if (mDraggedDownEntry != null) {
                 mDraggedDownEntry.setUserLocked(false);
@@ -3240,7 +3235,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 */);
     }
 
@@ -3260,14 +3255,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);
     }
 
@@ -3513,8 +3508,8 @@
     }
 
     private void updateKeyguardState() {
-        mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
-                mUnlockMethodCache.isMethodSecure(),
+        mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
+                mKeyguardStateController.isMethodSecure(),
                 mStatusBarKeyguardViewManager.isOccluded());
     }
 
@@ -3562,7 +3557,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 */);
             }
         }
@@ -3786,7 +3781,7 @@
 
     @Override
     public void showScreenPinningRequest(int taskId) {
-        if (mKeyguardMonitor.isShowing()) {
+        if (mKeyguardStateController.isShowing()) {
             // Don't allow apps to trigger this from keyguard.
             return;
         }
@@ -3909,7 +3904,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(
@@ -4539,6 +4534,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 50c3561..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 =
@@ -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);
@@ -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,8 +530,8 @@
      */
     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 (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
@@ -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 {
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 d8b2239..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);
@@ -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/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 c76f93e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ /dev/null
@@ -1,229 +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 com.android.systemui.Dependency;
-
-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 = Dependency.get(KeyguardUpdateMonitor.class);
-        Dependency.get(KeyguardUpdateMonitor.class).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/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 6dc90b9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ /dev/null
@@ -1,81 +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 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 e8b0f9b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ /dev/null
@@ -1,183 +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.content.Context;
-
-import com.android.internal.util.Preconditions;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
-
-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 KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
-    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;
-
-    /**
-     */
-    @Inject
-    public KeyguardMonitorImpl(Context context) {
-        mContext = context;
-        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
-    }
-
-    @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 isSecure() {
-        return mSecure;
-    }
-
-    @Override
-    public boolean isOccluded() {
-        return mOccluded;
-    }
-
-    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();
-    }
-
-    public boolean isDeviceInteractive() {
-        return mKeyguardUpdateMonitor.isDeviceInteractive();
-    }
-
-    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 dbfb09f..5da7267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -294,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);
@@ -320,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.
@@ -484,6 +485,7 @@
             Log.d(mTag, "updateTelephonySignalStrength: hasService=" +
                     Utils.isInService(mServiceState) + " ss=" + mSignalStrength);
         }
+        checkDefaultData();
         mCurrentState.connected = Utils.isInService(mServiceState)
                 && mSignalStrength != null;
         if (mCurrentState.connected) {
@@ -541,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;
 
@@ -617,7 +636,7 @@
         return candidateIconGroup;
     }
 
-    private boolean isDataDisabled() {
+    boolean isDataDisabled() {
         return !mPhone.isDataCapable();
     }
 
@@ -750,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) {
@@ -765,6 +785,7 @@
             carrierNetworkChangeMode = state.carrierNetworkChangeMode;
             userSetup = state.userSetup;
             roaming = state.roaming;
+            defaultDataOff = state.defaultDataOff;
         }
 
         @Override
@@ -781,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
@@ -796,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 e1b3816..7b5d489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -220,6 +220,7 @@
             @Override
             public void onMobileDataEnabled(boolean enabled) {
                 mCallbackHandler.setMobileDataEnabled(enabled);
+                notifyControllersMobileDataChanged();
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
@@ -386,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
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 4fa4b6c..95ae23c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,7 +62,6 @@
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.tiles.UserDetailView;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.UnlockMethodCache;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -93,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;
 
@@ -110,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);
@@ -149,7 +148,7 @@
         // Fetch initial values.
         mSettingsObserver.onChange(false);
 
-        keyguardMonitor.addCallback(mCallback);
+        keyguardStateController.addCallback(mCallback);
         listenForCallState();
 
         refreshUsers(UserHandle.USER_NULL);
@@ -597,20 +596,18 @@
     public static abstract class BaseUserAdapter extends BaseAdapter {
 
         final UserSwitcherController mController;
-        private final KeyguardMonitor mKeyguardMonitor;
-        private final UnlockMethodCache mUnlockMethodCache;
+        private final KeyguardStateController mKeyguardStateController;
 
         protected BaseUserAdapter(UserSwitcherController controller) {
             mController = controller;
-            mKeyguardMonitor = controller.mKeyguardMonitor;
-            mUnlockMethodCache = UnlockMethodCache.getInstance(controller.mContext);
+            mKeyguardStateController = controller.mKeyguardStateController;
             controller.addAdapter(new WeakReference<>(this));
         }
 
         public int getUserCount() {
-            boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
-                    && mKeyguardMonitor.isSecure()
-                    && !mUnlockMethodCache.canSkipBouncer();
+            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
+                    && mKeyguardStateController.isMethodSecure()
+                    && !mKeyguardStateController.canDismissLockScreen();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
@@ -630,9 +627,9 @@
 
         @Override
         public int getCount() {
-            boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
-                    && mKeyguardMonitor.isSecure()
-                    && !mUnlockMethodCache.canSkipBouncer();
+            boolean secureKeyguardShowing = mKeyguardStateController.isShowing()
+                    && mKeyguardStateController.isMethodSecure()
+                    && !mKeyguardStateController.canDismissLockScreen();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
@@ -819,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 9750790..453c2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -31,15 +31,21 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
 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);
 
@@ -51,8 +57,6 @@
             setActionBar(toolbar);
         }
 
-        Dependency.initDependencies(SystemUIFactory.getInstance().getRootComponent());
-
         if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) {
             final String action = getIntent().getAction();
             boolean showDemoMode = action != null && action.equals(
@@ -68,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/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/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 6d433d2..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,15 +37,18 @@
 
 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;
@@ -76,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, "");
@@ -281,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/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index b9d09ce..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(SystemUIFactory.getInstance().getRootComponent());
+        Dependency dependency = new Dependency();
+        SystemUIFactory
+                .getInstance().getRootComponent().createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index ccd2138..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(NotificationEntry.buildForTest(notification),
-                null, false);
+        mEntryListener.onEntryRemoved(
+                new NotificationEntryBuilder()
+                        .setSbn(notification)
+                        .build(),
+                null,
+                false);
     }
 
     private void entryAdded(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index 6db5ef9..fbb8e0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -38,10 +38,10 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.systemui.DumpController;
-import com.android.systemui.ScreenDecorations;
 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;
@@ -51,6 +51,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.EnumMap;
+import java.util.Map;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -60,11 +63,16 @@
 
     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() {
@@ -76,13 +84,23 @@
         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
@@ -95,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
@@ -110,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
@@ -124,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
@@ -139,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
@@ -153,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
@@ -167,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();
     }
 
@@ -184,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
@@ -202,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
@@ -216,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
@@ -230,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();
     }
 
@@ -247,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
@@ -262,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();
     }
 
@@ -283,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
@@ -297,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
@@ -316,8 +334,8 @@
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
 
         // Assert
-        verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController);
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verify(mMockTestBehavior).onModeActivated(mContext, mAssistHandleBehaviorController);
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 
     @Test
@@ -326,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
@@ -346,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/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
index fc18707..7a09137 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -164,6 +164,17 @@
     }
 
     @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());
 
@@ -255,7 +266,9 @@
         assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
         assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
         assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
-        assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
+
+        // TODO: Test restored text. Currently cannot test this, since it gets restored only after
+        // dialog size is known.
     }
 
     private Bundle buildBiometricPromptBundle() {
@@ -320,6 +333,11 @@
         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 {
@@ -347,5 +365,10 @@
         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
index 25e27ef..d4fc3f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -78,6 +78,13 @@
         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);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java
deleted file mode 100644
index 3ff1f38..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java
+++ /dev/null
@@ -1,201 +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.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 com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-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 AuthDialogCallback 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, AuthDialogCallback 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/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 238cfd7..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 NotificationEntry.buildForTest(sbn);
+        return new NotificationEntryBuilder().setSbn(sbn).build();
     }
 
     private void setCurrentTime(long time) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 04cf4bb..5757861 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -19,19 +19,16 @@
 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.NotificationListenerService.Ranking;
-import android.os.UserHandle;
-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.notification.collection.NotificationEntry;
 
 import org.junit.Before;
@@ -45,8 +42,6 @@
 @TestableLooper.RunWithLooper
 public class BubbleTest extends SysuiTestCase {
     @Mock
-    private StatusBarNotification mStatusBarNotification;
-    @Mock
     private Notification mNotif;
 
     private NotificationEntry mEntry;
@@ -57,28 +52,16 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mStatusBarNotification.getKey()).thenReturn("key");
-        when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
-        when(mStatusBarNotification.getUser()).thenReturn(new UserHandle(0));
         mExtras = new Bundle();
         mNotif.extras = mExtras;
 
-        mEntry = NotificationEntry.buildForTest(mStatusBarNotification);
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotif)
+                .build();
         mBubble = new Bubble(mContext, mEntry);
     }
 
     @Test
-    public void testInitialization() {
-        final Ranking ranking = new Ranking();
-
-        final NotificationEntry entry = new NotificationEntry(mStatusBarNotification, ranking);
-
-        assertEquals("key", entry.key());
-        assertEquals(mStatusBarNotification, entry.sbn());
-        assertEquals(ranking, entry.ranking());
-    }
-
-    @Test
     public void testGetUpdateMessage_default() {
         final String msg = "Hello there!";
         doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
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 1fbb443..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
@@ -76,6 +76,7 @@
     }
 
     @Test
+    @Ignore
     public void testExpansionAndCollapse() throws InterruptedException {
         Runnable afterExpand = Mockito.mock(Runnable.class);
         mExpandedController.expandFromStack(afterExpand);
@@ -93,6 +94,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildAdded() throws InterruptedException {
         expand();
 
@@ -105,6 +107,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildRemoved() throws InterruptedException {
         expand();
 
@@ -116,6 +119,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
         expand();
 
@@ -133,6 +137,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDismissed() throws InterruptedException {
         expand();
 
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 86554b1..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();
@@ -175,6 +180,7 @@
     }
 
     @Test
+    @Ignore
     public void testRemoveEndListeners() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -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/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index e1457ef..3561e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -34,6 +34,7 @@
 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;
@@ -46,8 +47,10 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class FalsingManagerProxyTest extends SysuiTestCase {
-    @Mock
+    @Mock(stubOnly = true)
     PluginManager mPluginManager;
+    @Mock(stubOnly = true)
+    ProximitySensor mProximitySensor;
     private Handler mHandler;
     private FalsingManagerProxy mProxy;
     private DeviceConfigProxy mDeviceConfig;
@@ -73,7 +76,8 @@
 
     @Test
     public void test_brightLineFalsingManagerDisabled() {
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig);
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
     }
 
@@ -82,13 +86,15 @@
         mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
         mTestableLooper.processAllMessages();
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig);
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
     }
 
     @Test
     public void test_brightLineFalsingManagerToggled() throws InterruptedException {
-        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mDeviceConfig);
+        mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
+                mDeviceConfig);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
 
         mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
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 c76fe74..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,8 +23,6 @@
 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;
@@ -32,17 +30,15 @@
 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
@@ -81,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));
     }
@@ -91,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));
     }
@@ -100,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));
     }
@@ -109,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));
     }
@@ -120,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));
@@ -142,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/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/qs/QSCarrierGroupTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java
index f29392b..a2a20a95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.os.Handler;
 import android.telephony.SubscriptionManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -28,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.CarrierTextController;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -45,13 +47,20 @@
 public class QSCarrierGroupTest extends LeakCheckedTest {
 
     private QSCarrierGroup mCarrierGroup;
+    private CarrierTextController.CarrierTextCallback mCallback;
+    private TestableLooper mTestableLooper;
 
     @Before
     public void setup() throws Exception {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
-        TestableLooper.get(this).runWithLooper(
+        mTestableLooper = TestableLooper.get(this);
+        mDependency.injectTestDependency(
+                Dependency.BG_HANDLER, new Handler(mTestableLooper.getLooper()));
+        mDependency.injectTestDependency(Dependency.MAIN_LOOPER, mTestableLooper.getLooper());
+        mTestableLooper.runWithLooper(
                 () -> mCarrierGroup = (QSCarrierGroup) LayoutInflater.from(mContext).inflate(
                         R.layout.qs_carrier_group, null));
+        mCallback = mCarrierGroup.getCallback();
     }
 
     @Test // throws no Exception
@@ -72,7 +81,7 @@
                 new CharSequence[]{""},
                 false,
                 new int[]{0});
-        spiedCarrierGroup.updateCarrierInfo(c1);
+        mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 1, subscriptionIds length 1, anySims true
         CarrierTextController.CarrierTextCallbackInfo
@@ -81,7 +90,7 @@
                 new CharSequence[]{""},
                 true,
                 new int[]{0});
-        spiedCarrierGroup.updateCarrierInfo(c2);
+        mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims false
         CarrierTextController.CarrierTextCallbackInfo
@@ -90,7 +99,7 @@
                 new CharSequence[]{"", ""},
                 false,
                 new int[]{0, 1});
-        spiedCarrierGroup.updateCarrierInfo(c3);
+        mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 2, subscriptionIds length 2, anySims true
         CarrierTextController.CarrierTextCallbackInfo
@@ -99,7 +108,9 @@
                 new CharSequence[]{"", ""},
                 true,
                 new int[]{0, 1});
-        spiedCarrierGroup.updateCarrierInfo(c4);
+        mCallback.updateCarrierInfo(c4);
+
+        mTestableLooper.processAllMessages();
     }
 
     @Test // throws no Exception
@@ -120,7 +131,7 @@
                 new CharSequence[]{"", ""},
                 false,
                 new int[]{0});
-        spiedCarrierGroup.updateCarrierInfo(c1);
+        mCallback.updateCarrierInfo(c1);
 
         // listOfCarriers length 2, subscriptionIds length 1, anySims true
         CarrierTextController.CarrierTextCallbackInfo
@@ -129,7 +140,7 @@
                 new CharSequence[]{"", ""},
                 true,
                 new int[]{0});
-        spiedCarrierGroup.updateCarrierInfo(c2);
+        mCallback.updateCarrierInfo(c2);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims false
         CarrierTextController.CarrierTextCallbackInfo
@@ -138,7 +149,7 @@
                 new CharSequence[]{""},
                 false,
                 new int[]{0, 1});
-        spiedCarrierGroup.updateCarrierInfo(c3);
+        mCallback.updateCarrierInfo(c3);
 
         // listOfCarriers length 1, subscriptionIds length 2, anySims true
         CarrierTextController.CarrierTextCallbackInfo
@@ -147,7 +158,8 @@
                 new CharSequence[]{""},
                 true,
                 new int[]{0, 1});
-        spiedCarrierGroup.updateCarrierInfo(c4);
+        mCallback.updateCarrierInfo(c4);
+        mTestableLooper.processAllMessages();
     }
 
     @Test // throws no Exception
@@ -161,7 +173,8 @@
                 new CharSequence[]{"", ""},
                 true,
                 new int[]{0, 1});
-        spiedCarrierGroup.updateCarrierInfo(c4);
+        mCallback.updateCarrierInfo(c4);
+        mTestableLooper.processAllMessages();
     }
 
     @Test // throws no Exception
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/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index e6f36e6..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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 2427cfc..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,7 +187,7 @@
     }
 
     @Test
-    public void unlockMethodCache_listenerUpdatesIndication() {
+    public void updateMonitor_listenerUpdatesIndication() {
         createController();
         String restingIndication = "Resting indication";
 
@@ -281,14 +203,14 @@
         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/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
index b044595..350ab5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -23,6 +23,7 @@
 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;
@@ -40,9 +41,7 @@
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.dreams.IDreamManager;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 
 import androidx.test.filters.SmallTest;
@@ -231,15 +230,17 @@
     public void testCanAlertCommon_false_suppressedForGroups() {
         ensureStateForAlertCommon();
 
-        Notification n = new Notification.Builder(getContext(), "a")
-                .setGroup("a")
-                .setGroupSummary(true)
-                .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+        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();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = NotificationEntry.buildForTest(sbn);
-        entry.importance = IMPORTANCE_DEFAULT;
 
         assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
     }
@@ -288,7 +289,9 @@
         ensureStateForHeadsUpWhenDozing();
 
         NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
-        entry.suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT;
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT)
+                .build();
 
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
@@ -379,7 +382,9 @@
         ensureStateForHeadsUpWhenAwake();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        entry.suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK;
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
+                .build();
 
         assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
     }
@@ -481,7 +486,9 @@
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createBubble();
-        entry.canBubble = false;
+        modifyRanking(entry)
+                .setCanBubble(false)
+                .build();
 
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
     }
@@ -494,7 +501,9 @@
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        entry.canBubble = true;
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
 
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
     }
@@ -507,8 +516,10 @@
         ensureStateForBubbleUp();
 
         NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
-        entry.canBubble = true;
-        entry.notification.getNotification().flags |= FLAG_BUBBLE;
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
+        entry.sbn().getNotification().flags |= FLAG_BUBBLE;
 
         assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
     }
@@ -549,13 +560,16 @@
                 .setContentText("content text")
                 .setBubbleMetadata(data)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = NotificationEntry.buildForTest(sbn);
-        entry.notification.getNotification().flags |= FLAG_BUBBLE;
-        entry.importance = IMPORTANCE_HIGH;
-        entry.canBubble = true;
-        return entry;
+        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) {
@@ -563,11 +577,14 @@
                 .setContentTitle("title")
                 .setContentText("content text")
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = NotificationEntry.buildForTest(sbn);
-        entry.importance = importance;
-        return entry;
+
+        return new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(importance)
+                .build();
     }
 
     /**
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 55ce8d6..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(NotificationEntry.buildForTest(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 99d09f1..852ddb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -63,7 +63,6 @@
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
 
     private TestableNotificationRemoteInputManager mRemoteInputManager;
-    private StatusBarNotification mSbn;
     private NotificationEntry mEntry;
     private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
     private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
@@ -78,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 = NotificationEntry.buildForTest(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,
@@ -94,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);
     }
@@ -175,7 +178,9 @@
         // Setup a notification entry with 1 remote input.
         StatusBarNotification newSbn =
                 mRemoteInputManager.rebuildNotificationWithRemoteInput(mEntry, "A Reply", false);
-        NotificationEntry entry = NotificationEntry.buildForTest(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 7cd5819..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;
@@ -187,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;
     }
 
@@ -295,17 +299,7 @@
         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());
+
         final NotificationChannel channel =
                 new NotificationChannel(
                         notification.getChannelId(),
@@ -313,14 +307,20 @@
                         importance);
         channel.setBlockableSystem(true);
 
-        NotificationEntry entry = new NotificationEntry(
-                sbn,
-                new RankingBuilder()
-                        .setKey(sbn.getKey())
-                        .setChannel(channel)
-                        .build());
+        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.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 388cf58..9e72504 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -120,7 +120,9 @@
 
     private NotificationEntry createEntry() throws Exception {
         ExpandableNotificationRow row = mHelper.createRow();
-        NotificationEntry entry = NotificationEntry.buildForTest(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
index bbdc4b7..820f465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -16,12 +16,16 @@
 
 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
@@ -33,49 +37,45 @@
     private boolean mMatchesInterruptionFilter = false;
     private int mVisibilityOverride = 0;
     private int mSuppressedVisualEffects = 0;
-    private int mImportance = 0;
+    @Importance private int mImportance = 0;
     private CharSequence mExplanation = "test_explanation";
     private String mOverrideGroupKey = null;
     private NotificationChannel mChannel = null;
-    private ArrayList<String> mOverridePeople = null;
+    private ArrayList<String> mAdditionalPeople = null;
     private ArrayList<SnoozeCriterion> mSnoozeCriteria = null;
-    private boolean mShowBadge = false;
+    private boolean mCanShowBadge = false;
     private int mUserSentiment = 0;
-    private boolean mHidden = false;
+    private boolean mIsSuspended = false;
     private long mLastAudiblyAlertedMs = 0;
     private boolean mNoisy = false;
-    private ArrayList<Notification.Action> mSmartActions = null;
-    private ArrayList<CharSequence> mSmartReplies = null;
+    private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
+    private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
     private boolean mCanBubble = false;
+    private boolean mIsVisuallyInterruptive = false;
 
-    public RankingBuilder setKey(String key) {
-        mKey = key;
-        return this;
+    public RankingBuilder() {
     }
 
-    public RankingBuilder setImportance(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(ArrayList<Notification.Action> smartActions) {
-        mSmartActions = smartActions;
-        return this;
-    }
-
-    public RankingBuilder setSmartReplies(ArrayList<CharSequence> smartReplies) {
-        mSmartReplies = smartReplies;
-        return this;
+    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() {
@@ -90,16 +90,131 @@
                 mExplanation,
                 mOverrideGroupKey,
                 mChannel,
-                mOverridePeople,
+                mAdditionalPeople,
                 mSnoozeCriteria,
-                mShowBadge,
+                mCanShowBadge,
                 mUserSentiment,
-                mHidden,
+                mIsSuspended,
                 mLastAudiblyAlertedMs,
                 mNoisy,
                 mSmartActions,
                 mSmartReplies,
-                mCanBubble);
+                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 e54ea6a..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 = NotificationEntry.buildForTest(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/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 36b143b..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 = NotificationEntry.buildForTest(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 b522316..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 = NotificationEntry.buildForTest(mMockStatusBarNotification);
-        entry.suspended = true;
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setSuspended(true)
+                .build();
+
         assertTrue(mNotificationFilter.shouldFilterOut(entry));
 
         // not hidden
-        entry = NotificationEntry.buildForTest(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 e42d155..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 NotificationEntry.buildForTest(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 49229ca..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 = NotificationEntry.buildForTest(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 45173a2..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();
@@ -231,57 +237,57 @@
     public void testIsExemptFromDndVisualSuppression_foreground() {
         initStatusBarNotification(false);
 
-        Notification n = mMockStatusBarNotification.getNotification();
-        n.flags = Notification.FLAG_FOREGROUND_SERVICE;
-        NotificationEntry entry = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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(systemGeneratedSmartActions, entry.getSmartActions());
         assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
-        assertEquals(snoozeCriterions, entry.snoozeCriteria);
+        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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index ff3a2e2..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 = NotificationEntry.buildForTest(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 a14557b..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;
@@ -51,7 +52,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationTestHelper;
-import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
@@ -328,11 +328,11 @@
         // Give each child a unique channel id/name.
         int i = 0;
         for (ExpandableNotificationRow childRow : childRows) {
-            childRow.getEntry().setRanking(new RankingBuilder()
+            modifyRanking(childRow.getEntry())
                     .setChannel(
                             new NotificationChannel(
                                     "id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT))
-                    .build());
+                    .build();
             i++;
         }
 
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 a26cdbd..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;
@@ -44,7 +46,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationTestHelper;
-import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.util.Assert;
 
@@ -130,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));
 
@@ -142,14 +145,16 @@
         ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
         int i = 0;
         for (ExpandableNotificationRow childRow : groupRow.getNotificationChildren()) {
-            childRow.getEntry().setRanking(new RankingBuilder()
+            modifyRanking(childRow.getEntry())
                     .setChannel(
                             new NotificationChannel(
                                     Integer.toString(i++), "", IMPORTANCE_DEFAULT))
-                    .build());
+                    .build();
         }
 
-        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(groupRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
 
@@ -159,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));
 
@@ -174,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));
@@ -185,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));
     }
@@ -194,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));
     }
@@ -202,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);
 
@@ -213,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));
     }
@@ -226,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));
     }
@@ -234,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/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 09c4179..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;
@@ -62,7 +64,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationTestHelper;
-import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -313,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();
 
@@ -342,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();
 
@@ -371,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();
@@ -402,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);
@@ -432,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();
 
@@ -499,10 +510,9 @@
 
         try {
             ExpandableNotificationRow row = mHelper.createRow(nb.build());
-            row.getEntry().setRanking(
-                    new RankingBuilder()
-                            .setChannel(mTestNotificationChannel)
-                            .build());
+            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 096acf9..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,7 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -61,11 +59,7 @@
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mRow = mock(ExpandableNotificationRow.class);
-        NotificationEntry entry = new NotificationEntry(
-                mock(StatusBarNotification.class),
-                new RankingBuilder()
-                        .setChannel(mock(NotificationChannel.class))
-                        .build());
+        NotificationEntry entry = new NotificationEntryBuilder().build();
         when(mRow.getEntry()).thenReturn(entry);
     }
 
@@ -75,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/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 de3623f..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 = NotificationEntry.buildForTest(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 c51263f..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
@@ -102,7 +103,7 @@
         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() {
@@ -384,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);
@@ -397,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/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index e33545b..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;
 
@@ -76,22 +76,18 @@
                 .setGroupSummary(isSummary)
                 .setGroup(TEST_GROUP_ID)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                0, /* uid */
-                0 /* initialPid */,
-                notif,
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
-        NotificationEntry entry = NotificationEntry.buildForTest(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 2f24494..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
@@ -131,7 +131,7 @@
                         mKeyguardBypassController);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
                 mKeyguardBypassController, mHeadsUpManager,
-                mock(NotificationRoundnessManager.class));
+                mock(NotificationRoundnessManager.class), mStatusBarStateController);
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
                 mKeyguardBypassController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
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 2623b46..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
@@ -48,7 +48,7 @@
 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;
 
@@ -103,7 +103,7 @@
                     mScrimInFrontColor = scrimInFrontColor;
                 },
                 visible -> mScrimVisibility = visible, mDozeParamenters, mAlarmManager,
-                mock(KeyguardMonitor.class));
+                mock(KeyguardStateController.class));
         mScrimController.setHasBackdrop(false);
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.KEYGUARD);
@@ -243,7 +243,7 @@
     }
 
     @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.
@@ -267,11 +267,22 @@
                 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();
-        assertScrimAlpha(TRANSPARENT /* front */,
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
                 SEMI_TRANSPARENT /* back */,
                 TRANSPARENT /* bubble */);
+
+        // Reset value since enums are static.
+        mScrimController.setAodFrontScrimAlpha(0f);
     }
 
     @Test
@@ -790,9 +801,9 @@
                 ScrimView scrimForBubble,
                 TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
                 Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
-                AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
+                AlarmManager alarmManager, KeyguardStateController keyguardStateController) {
             super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
-                    scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
+                    scrimVisibleListener, dozeParameters, alarmManager, keyguardStateController);
         }
 
         @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 3c445c8..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
@@ -46,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;
@@ -65,6 +66,8 @@
     @Mock
     private KeyguardBouncer mBouncer;
     @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
     private StatusBar mStatusBar;
     @Mock
     private ViewGroup mContainer;
@@ -90,6 +93,7 @@
         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));
@@ -169,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 5a6f27d..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
@@ -167,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);
@@ -200,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
@@ -263,7 +264,7 @@
 
         // Given
         sbn.getNotification().contentIntent = null;
-        when(mKeyguardMonitor.isShowing()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mShadeController.isOccluded()).thenReturn(true);
 
         // When
@@ -293,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 45cd1e1..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 = NotificationEntry.buildForTest(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 = NotificationEntry.buildForTest(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/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index a1afd1d..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,
@@ -254,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,
@@ -271,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();
@@ -314,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",
@@ -332,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();
 
@@ -351,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();
 
@@ -370,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();
 
@@ -389,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();
 
@@ -417,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 = NotificationEntry.buildForTest(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));
     }
@@ -438,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 = NotificationEntry.buildForTest(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));
     }
@@ -455,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 = NotificationEntry.buildForTest(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));
     }
@@ -473,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 = NotificationEntry.buildForTest(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));
     }
@@ -761,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,
@@ -788,7 +803,6 @@
                 KeyguardUpdateMonitor keyguardUpdateMonitor,
                 StatusBarWindowView statusBarWindow) {
             mStatusBarKeyguardViewManager = man;
-            mUnlockMethodCache = unlock;
             mKeyguardIndicationController = key;
             mStackScroller = stack;
             mPowerManager = pm;
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 bda0e39..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 = NotificationEntry.buildForTest(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/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 3ddfbda..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
@@ -147,7 +147,7 @@
     }
 
     @Test
-    public void testNoInternetIcon_withoutDefaultSub() {
+    public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
@@ -158,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();
@@ -173,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
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 8e3e2db..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;
@@ -53,6 +51,7 @@
 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;
@@ -66,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;
@@ -139,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 = NotificationEntry.buildForTest(sbn);
+
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotification)
+                .build();
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
     }
@@ -470,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);
     }
@@ -496,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,
@@ -516,6 +521,7 @@
         List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
                 useDelayedOnClickListener);
         smartSuggestions.addAll(mView.inflateSmartActions(
+                getContext(),
                 new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
                 mLogger,
                 mEntry,
@@ -555,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 */,
@@ -862,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/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 2fb0e0e..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
+++ /dev/null
@@ -1,83 +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;
-    }
-}
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/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/system_messages.proto b/proto/src/system_messages.proto
index ffbf1ae..5d6d1c9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -230,6 +230,10 @@
     // Package: android
     NOTE_TEST_HARNESS_MODE_ENABLED = 54;
 
+    // Inform the user that Serial Console is active.
+    // Package: android
+    NOTE_SERIAL_CONSOLE_ENABLED = 55;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
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 6953e86..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"],
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index cdb062d..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;
@@ -1173,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() {
@@ -1264,7 +1264,7 @@
         }
     }
 
-    private void notifyGestureInternal(AccessibilityGestureInfo gestureInfo) {
+    private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
@@ -1469,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 feb7329..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;
@@ -828,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;
         }
@@ -1028,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
@@ -1042,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;
             }
         }
@@ -2200,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/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 a338b90..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,7 +61,7 @@
 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";
@@ -109,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;
@@ -148,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());
@@ -197,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.
         }
@@ -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,7 +279,7 @@
         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);
     }
@@ -318,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(
@@ -338,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;
     }
@@ -371,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;
             }
         }
@@ -417,7 +418,7 @@
         if (!mGestureDetector.firstTapDetected() && mState.isClear()) {
             mSendTouchExplorationEndDelayed.forceSendAndRemove();
             mSendTouchInteractionEndDelayed.forceSendAndRemove();
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
+            mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
         } else {
             // Let gesture to handle to avoid duplicated TYPE_TOUCH_INTERACTION_END event.
             mSendTouchInteractionEndDelayed.cancel();
@@ -536,18 +537,19 @@
                     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 two pointers are delegated to the view hierarchy.
                 mState.startDelegating();
                 event = MotionEvent.obtainNoHistory(event);
-                sendDownForAllNotInjectedPointers(event, policyFlags);
+                mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags);
                 break;
         }
     }
@@ -585,7 +587,8 @@
             case 1:
             // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+                mDispatcher.sendMotionEvent(
+                        event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
                 break;
             case 2:
                 if (mSendHoverEnterAndMoveDelayed.isPending()) {
@@ -658,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) {
@@ -672,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
@@ -695,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;
@@ -716,20 +711,22 @@
                  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);
                 }
             } break;
         }
@@ -751,16 +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);
 
             } 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);
             }
         }
     }
@@ -769,57 +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();
     }
 
-    /**
-     * 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);
-            if (DEBUG) {
-                Slog.d(
-                        LOG_TAG,
-                        "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
-            }
-        }
-        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
@@ -828,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);
         }
     }
 
@@ -845,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.
@@ -978,10 +835,34 @@
                 MAX_DRAGGING_ANGLE_COS);
     }
 
+    /**
+     * 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.
      */
@@ -998,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();
         }
     }
@@ -1055,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,
@@ -1069,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,
@@ -1129,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();
@@ -1173,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 17e969a..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,14 +31,12 @@
  * 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;
 
@@ -71,13 +71,9 @@
     // 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. */
@@ -85,16 +81,6 @@
         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);
     }
 
     /**
@@ -226,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/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/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/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
new file mode 100644
index 0000000..de48f4b
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -0,0 +1,728 @@
+/*
+ * 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.backup;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
+import com.android.server.backup.transport.TransportNotAvailableException;
+import com.android.server.backup.transport.TransportNotRegisteredException;
+import com.android.server.backup.transport.TransportStats;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/** Handles in-memory bookkeeping of all BackupTransport objects. */
+public class TransportManager {
+    private static final String TAG = "BackupTransportManager";
+
+    @VisibleForTesting
+    public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+    private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+    private final @UserIdInt int mUserId;
+    private final PackageManager mPackageManager;
+    private final Set<ComponentName> mTransportWhitelist;
+    private final TransportClientManager mTransportClientManager;
+    private final TransportStats mTransportStats;
+    private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
+
+    /**
+     * Lock for registered transports and currently selected transport.
+     *
+     * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport
+     * code being executed such as {@link TransportClient#connect(String)}} and its variants should
+     * be made with this lock held, risk of deadlock.
+     */
+    private final Object mTransportLock = new Object();
+
+    /** @see #getRegisteredTransportNames() */
+    @GuardedBy("mTransportLock")
+    private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
+            new ArrayMap<>();
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private volatile String mCurrentTransportName;
+
+    TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist,
+            String selectedTransport) {
+        mUserId = userId;
+        mPackageManager = context.getPackageManager();
+        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
+        mCurrentTransportName = selectedTransport;
+        mTransportStats = new TransportStats();
+        mTransportClientManager = TransportClientManager.createEncryptingClientManager(mUserId,
+                context, mTransportStats);
+    }
+
+    @VisibleForTesting
+    TransportManager(
+            @UserIdInt int userId,
+            Context context,
+            Set<ComponentName> whitelist,
+            String selectedTransport,
+            TransportClientManager transportClientManager) {
+        mUserId = userId;
+        mPackageManager = context.getPackageManager();
+        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
+        mCurrentTransportName = selectedTransport;
+        mTransportStats = new TransportStats();
+        mTransportClientManager = transportClientManager;
+    }
+
+    /* Sets a listener to be called whenever a transport is registered. */
+    public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
+        mOnTransportRegisteredListener = listener;
+    }
+
+    @WorkerThread
+    void onPackageAdded(String packageName) {
+        registerTransportsFromPackage(packageName, transportComponent -> true);
+    }
+
+    void onPackageRemoved(String packageName) {
+        synchronized (mTransportLock) {
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
+        }
+    }
+
+    @WorkerThread
+    void onPackageChanged(String packageName, String... components) {
+        // Unfortunately this can't be atomic because we risk a deadlock if
+        // registerTransportsFromPackage() is put inside the synchronized block
+        Set<ComponentName> transportComponents = new ArraySet<>(components.length);
+        for (String componentName : components) {
+            transportComponents.add(new ComponentName(packageName, componentName));
+        }
+        synchronized (mTransportLock) {
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
+        }
+        registerTransportsFromPackage(packageName, transportComponents::contains);
+    }
+
+    /**
+     * Returns the {@link ComponentName}s of the registered transports.
+     *
+     * <p>A *registered* transport is a transport that satisfies intent with action
+     * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)}
+     * and that we have successfully connected to once.
+     */
+    ComponentName[] getRegisteredTransportComponents() {
+        synchronized (mTransportLock) {
+            return mRegisteredTransportsDescriptionMap
+                    .keySet()
+                    .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
+        }
+    }
+
+    /**
+     * Returns the names of the registered transports.
+     *
+     * @see #getRegisteredTransportComponents()
+     */
+    String[] getRegisteredTransportNames() {
+        synchronized (mTransportLock) {
+            String[] transportNames = new String[mRegisteredTransportsDescriptionMap.size()];
+            int i = 0;
+            for (TransportDescription description : mRegisteredTransportsDescriptionMap.values()) {
+                transportNames[i] = description.name;
+                i++;
+            }
+            return transportNames;
+        }
+    }
+
+    /** Returns a set with the whitelisted transports. */
+    Set<ComponentName> getTransportWhitelist() {
+        return mTransportWhitelist;
+    }
+
+    /** Returns the name of the selected transport or {@code null} if no transport selected. */
+    @Nullable
+    public String getCurrentTransportName() {
+        return mCurrentTransportName;
+    }
+
+    /**
+     * Returns the {@link ComponentName} of the host service of the selected transport or
+     * {@code null} if no transport selected.
+     *
+     * @throws TransportNotRegisteredException if the selected transport is not registered.
+     */
+    @Nullable
+    public ComponentName getCurrentTransportComponent()
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            if (mCurrentTransportName == null) {
+                return null;
+            }
+            return getRegisteredTransportComponentOrThrowLocked(mCurrentTransportName);
+        }
+    }
+
+    /**
+     * Returns the transport name associated with {@code transportComponent}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent).name;
+        }
+    }
+
+    /**
+     * Retrieves the transport dir name of {@code transportComponent}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
+                    .transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the transport dir name of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(String transportName) throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the configuration intent of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    @Nullable
+    public Intent getTransportConfigurationIntent(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .configurationIntent;
+        }
+    }
+
+    /**
+     * Retrieves the current destination string of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportCurrentDestinationString(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .currentDestinationString;
+        }
+    }
+
+    /**
+     * Retrieves the data management intent of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    @Nullable
+    public Intent getTransportDataManagementIntent(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .dataManagementIntent;
+        }
+    }
+
+    /**
+     * Retrieves the data management label of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    @Nullable
+    public CharSequence getTransportDataManagementLabel(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .dataManagementLabel;
+        }
+    }
+
+    /* Returns true if the transport identified by {@code transportName} is registered. */
+    public boolean isTransportRegistered(String transportName) {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportEntryLocked(transportName) != null;
+        }
+    }
+
+    /**
+     * Execute {@code transportConsumer} for each registered transport passing the transport name.
+     * This is called with an internal lock held, ensuring that the transport will remain registered
+     * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
+     * transportConsumer}.
+     *
+     * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of
+     * {@link TransportClient#connect(String)} here, otherwise you risk deadlock.
+     */
+    public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
+        synchronized (mTransportLock) {
+            for (TransportDescription transportDescription :
+                    mRegisteredTransportsDescriptionMap.values()) {
+                transportConsumer.accept(transportDescription.name);
+            }
+        }
+    }
+
+    /**
+     * Updates given values for the transport already registered and identified with {@param
+     * transportComponent}. If the transport is not registered it will log and return.
+     */
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            @Nullable CharSequence dataManagementLabel) {
+        synchronized (mTransportLock) {
+            TransportDescription description =
+                    mRegisteredTransportsDescriptionMap.get(transportComponent);
+            if (description == null) {
+                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
+                return;
+            }
+            description.name = name;
+            description.configurationIntent = configurationIntent;
+            description.currentDestinationString = currentDestinationString;
+            description.dataManagementIntent = dataManagementIntent;
+            description.dataManagementLabel = dataManagementLabel;
+            Slog.d(TAG, "Transport " + name + " updated its attributes");
+        }
+    }
+
+    @GuardedBy("mTransportLock")
+    private ComponentName getRegisteredTransportComponentOrThrowLocked(String transportName)
+            throws TransportNotRegisteredException {
+        ComponentName transportComponent = getRegisteredTransportComponentLocked(transportName);
+        if (transportComponent == null) {
+            throw new TransportNotRegisteredException(transportName);
+        }
+        return transportComponent;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            ComponentName transportComponent) throws TransportNotRegisteredException {
+        TransportDescription description =
+                mRegisteredTransportsDescriptionMap.get(transportComponent);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportComponent);
+        }
+        return description;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            String transportName) throws TransportNotRegisteredException {
+        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportName);
+        }
+        return description;
+    }
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private ComponentName getRegisteredTransportComponentLocked(String transportName) {
+        Map.Entry<ComponentName, TransportDescription> entry =
+                getRegisteredTransportEntryLocked(transportName);
+        return (entry == null) ? null : entry.getKey();
+    }
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private TransportDescription getRegisteredTransportDescriptionLocked(String transportName) {
+        Map.Entry<ComponentName, TransportDescription> entry =
+                getRegisteredTransportEntryLocked(transportName);
+        return (entry == null) ? null : entry.getValue();
+    }
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
+            String transportName) {
+        for (Map.Entry<ComponentName, TransportDescription> entry :
+                mRegisteredTransportsDescriptionMap.entrySet()) {
+            TransportDescription description = entry.getValue();
+            if (transportName.equals(description.name)) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not
+     * registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not registered.
+     */
+    @Nullable
+    public TransportClient getTransportClient(String transportName, String caller) {
+        try {
+            return getTransportClientOrThrow(transportName, caller);
+        } catch (TransportNotRegisteredException e) {
+            Slog.w(TAG, "Transport " + transportName + " not registered");
+            return null;
+        }
+    }
+
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or throws if not registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public TransportClient getTransportClientOrThrow(String transportName, String caller)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            ComponentName component = getRegisteredTransportComponentLocked(transportName);
+            if (component == null) {
+                throw new TransportNotRegisteredException(transportName);
+            }
+            return mTransportClientManager.getTransportClient(component, caller);
+        }
+    }
+
+    /**
+     * Returns a {@link TransportClient} for the current transport or {@code null} if not
+     * registered.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not registered.
+     * @throws IllegalStateException if no transport is selected.
+     */
+    @Nullable
+    public TransportClient getCurrentTransportClient(String caller) {
+        if (mCurrentTransportName == null) {
+            throw new IllegalStateException("No transport selected");
+        }
+        synchronized (mTransportLock) {
+            return getTransportClient(mCurrentTransportName, caller);
+        }
+    }
+
+    /**
+     * Returns a {@link TransportClient} for the current transport or throws if not registered.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     * @throws IllegalStateException if no transport is selected.
+     */
+    public TransportClient getCurrentTransportClientOrThrow(String caller)
+            throws TransportNotRegisteredException {
+        if (mCurrentTransportName == null) {
+            throw new IllegalStateException("No transport selected");
+        }
+        synchronized (mTransportLock) {
+            return getTransportClientOrThrow(mCurrentTransportName, caller);
+        }
+    }
+
+    /**
+     * Disposes of the {@link TransportClient}.
+     *
+     * @param transportClient The {@link TransportClient} to be disposed of.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     */
+    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+        mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+    }
+
+    /**
+     * Sets {@code transportName} as selected transport and returns previously selected transport
+     * name. If there was no previous transport it returns null.
+     *
+     * <p>You should NOT call this method in new code. This won't make any checks against {@code
+     * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or
+     * another error at the time it's being executed.
+     *
+     * <p>{@link Deprecated} as public, this method can be used as private.
+     */
+    @Deprecated
+    @Nullable
+    String selectTransport(String transportName) {
+        synchronized (mTransportLock) {
+            String prevTransport = mCurrentTransportName;
+            mCurrentTransportName = transportName;
+            return prevTransport;
+        }
+    }
+
+    /**
+     * Tries to register the transport if not registered. If successful also selects the transport.
+     *
+     * @param transportComponent Host of the transport.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    @WorkerThread
+    public int registerAndSelectTransport(ComponentName transportComponent) {
+        // If it's already registered we select and return
+        synchronized (mTransportLock) {
+            try {
+                selectTransport(getTransportName(transportComponent));
+                return BackupManager.SUCCESS;
+            } catch (TransportNotRegisteredException e) {
+                // Fall through and release lock
+            }
+        }
+
+        // We can't call registerTransport() with the transport lock held
+        int result = registerTransport(transportComponent);
+        if (result != BackupManager.SUCCESS) {
+            return result;
+        }
+        synchronized (mTransportLock) {
+            try {
+                selectTransport(getTransportName(transportComponent));
+                return BackupManager.SUCCESS;
+            } catch (TransportNotRegisteredException e) {
+                Slog.wtf(TAG, "Transport got unregistered");
+                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+            }
+        }
+    }
+
+    @WorkerThread
+    public void registerTransports() {
+        registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true);
+    }
+
+    @WorkerThread
+    private void registerTransportsFromPackage(
+            String packageName, Predicate<ComponentName> transportComponentFilter) {
+        try {
+            mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Trying to register transports from package not found " + packageName);
+            return;
+        }
+
+        registerTransportsForIntent(
+                new Intent(mTransportServiceIntent).setPackage(packageName),
+                transportComponentFilter.and(fromPackageFilter(packageName)));
+    }
+
+    @WorkerThread
+    private void registerTransportsForIntent(
+            Intent intent, Predicate<ComponentName> transportComponentFilter) {
+        List<ResolveInfo> hosts =
+                mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId);
+        if (hosts == null) {
+            return;
+        }
+        for (ResolveInfo host : hosts) {
+            ComponentName transportComponent = host.serviceInfo.getComponentName();
+            if (transportComponentFilter.test(transportComponent)
+                    && isTransportTrusted(transportComponent)) {
+                registerTransport(transportComponent);
+            }
+        }
+    }
+
+    /** Transport has to be whitelisted and privileged. */
+    private boolean isTransportTrusted(ComponentName transport) {
+        if (!mTransportWhitelist.contains(transport)) {
+            Slog.w(
+                    TAG,
+                    "BackupTransport " + transport.flattenToShortString() + " not whitelisted.");
+            return false;
+        }
+        try {
+            PackageInfo packInfo =
+                    mPackageManager.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId);
+            if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+                    == 0) {
+                Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Package not found.", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Tries to register transport represented by {@code transportComponent}.
+     *
+     * <p><b>Warning:</b> Don't call this with the transport lock held.
+     *
+     * @param transportComponent Host of the transport that we want to register.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    @WorkerThread
+    private int registerTransport(ComponentName transportComponent) {
+        checkCanUseTransport();
+
+        if (!isTransportTrusted(transportComponent)) {
+            return BackupManager.ERROR_TRANSPORT_INVALID;
+        }
+
+        String transportString = transportComponent.flattenToShortString();
+        String callerLogString = "TransportManager.registerTransport()";
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION, true);
+
+        TransportClient transportClient =
+                mTransportClientManager.getTransportClient(
+                        transportComponent, extras, callerLogString);
+        final IBackupTransport transport;
+        try {
+            transport = transportClient.connectOrThrow(callerLogString);
+        } catch (TransportNotAvailableException e) {
+            Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration");
+            mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
+            return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+        }
+
+        int result;
+        try {
+            String transportName = transport.name();
+            String transportDirName = transport.transportDirName();
+            registerTransport(transportComponent, transport);
+            // If registerTransport() hasn't thrown...
+            Slog.d(TAG, "Transport " + transportString + " registered");
+            mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
+            result = BackupManager.SUCCESS;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Transport " + transportString + " died while registering");
+            result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
+        }
+
+        mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
+        return result;
+    }
+
+    /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
+    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
+            throws RemoteException {
+        checkCanUseTransport();
+
+        TransportDescription description =
+                new TransportDescription(
+                        transport.name(),
+                        transport.transportDirName(),
+                        transport.configurationIntent(),
+                        transport.currentDestinationString(),
+                        transport.dataManagementIntent(),
+                        transport.dataManagementIntentLabel());
+        synchronized (mTransportLock) {
+            mRegisteredTransportsDescriptionMap.put(transportComponent, description);
+        }
+    }
+
+    private void checkCanUseTransport() {
+        Preconditions.checkState(
+                !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held");
+    }
+
+    public void dumpTransportClients(PrintWriter pw) {
+        mTransportClientManager.dump(pw);
+    }
+
+    public void dumpTransportStats(PrintWriter pw) {
+        mTransportStats.dump(pw);
+    }
+
+    private static Predicate<ComponentName> fromPackageFilter(String packageName) {
+        return transportComponent -> packageName.equals(transportComponent.getPackageName());
+    }
+
+    private static class TransportDescription {
+        private String name;
+        private final String transportDirName;
+        @Nullable private Intent configurationIntent;
+        private String currentDestinationString;
+        @Nullable private Intent dataManagementIntent;
+        @Nullable private CharSequence dataManagementLabel;
+
+        private TransportDescription(
+                String name,
+                String transportDirName,
+                @Nullable Intent configurationIntent,
+                String currentDestinationString,
+                @Nullable Intent dataManagementIntent,
+                @Nullable CharSequence dataManagementLabel) {
+            this.name = name;
+            this.transportDirName = transportDirName;
+            this.configurationIntent = configurationIntent;
+            this.currentDestinationString = currentDestinationString;
+            this.dataManagementIntent = dataManagementIntent;
+            this.dataManagementLabel = dataManagementLabel;
+        }
+    }
+}
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/backuplib/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 0000000..72b1ee7
--- /dev/null
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,206 @@
+/*
+ * 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.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+import static com.android.server.backup.transport.TransportUtils.formatMessage;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Function;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ */
+public class TransportClientManager {
+    private static final String TAG = "TransportClientManager";
+    private static final String SERVICE_ACTION_ENCRYPTING_TRANSPORT =
+            "android.encryption.BACKUP_ENCRYPTION";
+    private static final ComponentName ENCRYPTING_TRANSPORT = new ComponentName(
+            "com.android.server.backup.encryption",
+            "com.android.server.backup.encryption.BackupEncryptionService");
+    private static final String ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY = "transport";
+
+    private final @UserIdInt int mUserId;
+    private final Context mContext;
+    private final TransportStats mTransportStats;
+    private final Object mTransportClientsLock = new Object();
+    private int mTransportClientsCreated = 0;
+    private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
+    private final Function<ComponentName, Intent> mIntentFunction;
+
+    /**
+     * Return an {@link Intent} which resolves to an intermediate {@link IBackupTransport} that
+     * encrypts (or decrypts) the data when sending it (or receiving it) from the {@link
+     * IBackupTransport} for the given {@link ComponentName}.
+     */
+    public static Intent getEncryptingTransportIntent(ComponentName tranportComponent) {
+        return new Intent(SERVICE_ACTION_ENCRYPTING_TRANSPORT)
+                .setComponent(ENCRYPTING_TRANSPORT)
+                .putExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY, tranportComponent);
+    }
+
+    /**
+     * Return an {@link Intent} which resolves to the {@link IBackupTransport} for the {@link
+     * ComponentName}.
+     */
+    private static Intent getRealTransportIntent(ComponentName transportComponent) {
+        return new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+    }
+
+    /**
+     * Given a {@link Intent} originally created by {@link
+     * #getEncryptingTransportIntent(ComponentName)}, returns the {@link Intent} which resolves to
+     * the {@link IBackupTransport} for that {@link ComponentName}.
+     */
+    public static Intent getRealTransportIntent(Intent encryptingTransportIntent) {
+        ComponentName transportComponent = encryptingTransportIntent.getParcelableExtra(
+                ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
+        Intent intent = getRealTransportIntent(transportComponent)
+                .putExtras(encryptingTransportIntent.getExtras());
+        intent.removeExtra(ENCRYPTING_TRANSPORT_REAL_TRANSPORT_KEY);
+        return intent;
+    }
+
+    /**
+     * Create a {@link TransportClientManager} such that {@link #getTransportClient(ComponentName,
+     * Bundle, String)} returns a {@link TransportClient} which connects to an intermediate {@link
+     * IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
+     * the {@link IBackupTransport} for the given {@link ComponentName}.
+     */
+    public static TransportClientManager createEncryptingClientManager(@UserIdInt int userId,
+            Context context, TransportStats transportStats) {
+        return new TransportClientManager(userId, context, transportStats,
+                TransportClientManager::getEncryptingTransportIntent);
+    }
+
+    public TransportClientManager(@UserIdInt int userId, Context context,
+            TransportStats transportStats) {
+        this(userId, context, transportStats, TransportClientManager::getRealTransportIntent);
+    }
+
+    private TransportClientManager(@UserIdInt int userId, Context context,
+            TransportStats transportStats, Function<ComponentName, Intent> intentFunction) {
+        mUserId = userId;
+        mContext = context;
+        mTransportStats = transportStats;
+        mIntentFunction = intentFunction;
+    }
+
+    /**
+     * Retrieves a {@link TransportClient} for the transport identified by {@param
+     * transportComponent}.
+     *
+     * @param transportComponent The {@link ComponentName} of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     */
+    public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+        return getTransportClient(transportComponent, null, caller);
+    }
+
+    /**
+     * Retrieves a {@link TransportClient} for the transport identified by {@param
+     * transportComponent} whose binding intent will have the {@param extras} extras.
+     *
+     * @param transportComponent The {@link ComponentName} of the transport.
+     * @param extras A {@link Bundle} of extras to pass to the binding intent.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     */
+    public TransportClient getTransportClient(
+            ComponentName transportComponent, @Nullable Bundle extras, String caller) {
+        Intent bindIntent = mIntentFunction.apply(transportComponent);
+        if (extras != null) {
+            bindIntent.putExtras(extras);
+        }
+        return getTransportClient(transportComponent, caller, bindIntent);
+    }
+
+    private TransportClient getTransportClient(
+            ComponentName transportComponent, String caller, Intent bindIntent) {
+        synchronized (mTransportClientsLock) {
+            TransportClient transportClient =
+                    new TransportClient(
+                            mUserId,
+                            mContext,
+                            mTransportStats,
+                            bindIntent,
+                            transportComponent,
+                            Integer.toString(mTransportClientsCreated),
+                            caller);
+            mTransportClientsCallerMap.put(transportClient, caller);
+            mTransportClientsCreated++;
+            TransportUtils.log(
+                    Priority.DEBUG,
+                    TAG,
+                    formatMessage(null, caller, "Retrieving " + transportClient));
+            return transportClient;
+        }
+    }
+
+    /**
+     * Disposes of the {@link TransportClient}.
+     *
+     * @param transportClient The {@link TransportClient} to be disposed of.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     */
+    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+        transportClient.unbind(caller);
+        transportClient.markAsDisposed();
+        synchronized (mTransportClientsLock) {
+            TransportUtils.log(
+                    Priority.DEBUG,
+                    TAG,
+                    formatMessage(null, caller, "Disposing of " + transportClient));
+            mTransportClientsCallerMap.remove(transportClient);
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("Transport clients created: " + mTransportClientsCreated);
+        synchronized (mTransportClientsLock) {
+            pw.println("Current transport clients: " + mTransportClientsCallerMap.size());
+            for (TransportClient transportClient : mTransportClientsCallerMap.keySet()) {
+                String caller = mTransportClientsCallerMap.get(transportClient);
+                pw.println("    " + transportClient + " [" + caller + "]");
+                for (String logEntry : transportClient.getLogBuffer()) {
+                    pw.println("        " + logEntry);
+                }
+            }
+        }
+    }
+}
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/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
deleted file mode 100644
index 30ce4cf..0000000
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ /dev/null
@@ -1,727 +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.backup;
-
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.annotation.WorkerThread;
-import android.app.backup.BackupManager;
-import android.app.backup.BackupTransport;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
-import com.android.internal.util.Preconditions;
-import com.android.server.backup.transport.OnTransportRegisteredListener;
-import com.android.server.backup.transport.TransportClient;
-import com.android.server.backup.transport.TransportClientManager;
-import com.android.server.backup.transport.TransportConnectionListener;
-import com.android.server.backup.transport.TransportNotAvailableException;
-import com.android.server.backup.transport.TransportNotRegisteredException;
-import com.android.server.backup.transport.TransportStats;
-
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/** Handles in-memory bookkeeping of all BackupTransport objects. */
-public class TransportManager {
-    private static final String TAG = "BackupTransportManager";
-
-    @VisibleForTesting
-    public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
-
-    private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
-    private final @UserIdInt int mUserId;
-    private final PackageManager mPackageManager;
-    private final Set<ComponentName> mTransportWhitelist;
-    private final TransportClientManager mTransportClientManager;
-    private final TransportStats mTransportStats;
-    private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
-
-    /**
-     * Lock for registered transports and currently selected transport.
-     *
-     * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport
-     * code being executed such as {@link TransportClient#connect(String)}} and its variants should
-     * be made with this lock held, risk of deadlock.
-     */
-    private final Object mTransportLock = new Object();
-
-    /** @see #getRegisteredTransportNames() */
-    @GuardedBy("mTransportLock")
-    private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
-            new ArrayMap<>();
-
-    @GuardedBy("mTransportLock")
-    @Nullable
-    private volatile String mCurrentTransportName;
-
-    TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist,
-            String selectedTransport) {
-        mUserId = userId;
-        mPackageManager = context.getPackageManager();
-        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
-        mCurrentTransportName = selectedTransport;
-        mTransportStats = new TransportStats();
-        mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
-    }
-
-    @VisibleForTesting
-    TransportManager(
-            @UserIdInt int userId,
-            Context context,
-            Set<ComponentName> whitelist,
-            String selectedTransport,
-            TransportClientManager transportClientManager) {
-        mUserId = userId;
-        mPackageManager = context.getPackageManager();
-        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
-        mCurrentTransportName = selectedTransport;
-        mTransportStats = new TransportStats();
-        mTransportClientManager = transportClientManager;
-    }
-
-    /* Sets a listener to be called whenever a transport is registered. */
-    public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
-        mOnTransportRegisteredListener = listener;
-    }
-
-    @WorkerThread
-    void onPackageAdded(String packageName) {
-        registerTransportsFromPackage(packageName, transportComponent -> true);
-    }
-
-    void onPackageRemoved(String packageName) {
-        synchronized (mTransportLock) {
-            mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
-        }
-    }
-
-    @WorkerThread
-    void onPackageChanged(String packageName, String... components) {
-        // Unfortunately this can't be atomic because we risk a deadlock if
-        // registerTransportsFromPackage() is put inside the synchronized block
-        Set<ComponentName> transportComponents = new ArraySet<>(components.length);
-        for (String componentName : components) {
-            transportComponents.add(new ComponentName(packageName, componentName));
-        }
-        synchronized (mTransportLock) {
-            mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
-        }
-        registerTransportsFromPackage(packageName, transportComponents::contains);
-    }
-
-    /**
-     * Returns the {@link ComponentName}s of the registered transports.
-     *
-     * <p>A *registered* transport is a transport that satisfies intent with action
-     * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)}
-     * and that we have successfully connected to once.
-     */
-    ComponentName[] getRegisteredTransportComponents() {
-        synchronized (mTransportLock) {
-            return mRegisteredTransportsDescriptionMap
-                    .keySet()
-                    .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
-        }
-    }
-
-    /**
-     * Returns the names of the registered transports.
-     *
-     * @see #getRegisteredTransportComponents()
-     */
-    String[] getRegisteredTransportNames() {
-        synchronized (mTransportLock) {
-            String[] transportNames = new String[mRegisteredTransportsDescriptionMap.size()];
-            int i = 0;
-            for (TransportDescription description : mRegisteredTransportsDescriptionMap.values()) {
-                transportNames[i] = description.name;
-                i++;
-            }
-            return transportNames;
-        }
-    }
-
-    /** Returns a set with the whitelisted transports. */
-    Set<ComponentName> getTransportWhitelist() {
-        return mTransportWhitelist;
-    }
-
-    /** Returns the name of the selected transport or {@code null} if no transport selected. */
-    @Nullable
-    public String getCurrentTransportName() {
-        return mCurrentTransportName;
-    }
-
-    /**
-     * Returns the {@link ComponentName} of the host service of the selected transport or
-     * {@code null} if no transport selected.
-     *
-     * @throws TransportNotRegisteredException if the selected transport is not registered.
-     */
-    @Nullable
-    public ComponentName getCurrentTransportComponent()
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            if (mCurrentTransportName == null) {
-                return null;
-            }
-            return getRegisteredTransportComponentOrThrowLocked(mCurrentTransportName);
-        }
-    }
-
-    /**
-     * Returns the transport name associated with {@code transportComponent}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportName(ComponentName transportComponent)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent).name;
-        }
-    }
-
-    /**
-     * Retrieves the transport dir name of {@code transportComponent}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(ComponentName transportComponent)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
-                    .transportDirName;
-        }
-    }
-
-    /**
-     * Retrieves the transport dir name of {@code transportName}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(String transportName) throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName;
-        }
-    }
-
-    /**
-     * Retrieves the configuration intent of {@code transportName}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    @Nullable
-    public Intent getTransportConfigurationIntent(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .configurationIntent;
-        }
-    }
-
-    /**
-     * Retrieves the current destination string of {@code transportName}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportCurrentDestinationString(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .currentDestinationString;
-        }
-    }
-
-    /**
-     * Retrieves the data management intent of {@code transportName}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    @Nullable
-    public Intent getTransportDataManagementIntent(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .dataManagementIntent;
-        }
-    }
-
-    /**
-     * Retrieves the data management label of {@code transportName}.
-     *
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    @Nullable
-    public CharSequence getTransportDataManagementLabel(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .dataManagementLabel;
-        }
-    }
-
-    /* Returns true if the transport identified by {@code transportName} is registered. */
-    public boolean isTransportRegistered(String transportName) {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportEntryLocked(transportName) != null;
-        }
-    }
-
-    /**
-     * Execute {@code transportConsumer} for each registered transport passing the transport name.
-     * This is called with an internal lock held, ensuring that the transport will remain registered
-     * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
-     * transportConsumer}.
-     *
-     * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of
-     * {@link TransportClient#connect(String)} here, otherwise you risk deadlock.
-     */
-    public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
-        synchronized (mTransportLock) {
-            for (TransportDescription transportDescription :
-                    mRegisteredTransportsDescriptionMap.values()) {
-                transportConsumer.accept(transportDescription.name);
-            }
-        }
-    }
-
-    /**
-     * Updates given values for the transport already registered and identified with {@param
-     * transportComponent}. If the transport is not registered it will log and return.
-     */
-    public void updateTransportAttributes(
-            ComponentName transportComponent,
-            String name,
-            @Nullable Intent configurationIntent,
-            String currentDestinationString,
-            @Nullable Intent dataManagementIntent,
-            @Nullable CharSequence dataManagementLabel) {
-        synchronized (mTransportLock) {
-            TransportDescription description =
-                    mRegisteredTransportsDescriptionMap.get(transportComponent);
-            if (description == null) {
-                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
-                return;
-            }
-            description.name = name;
-            description.configurationIntent = configurationIntent;
-            description.currentDestinationString = currentDestinationString;
-            description.dataManagementIntent = dataManagementIntent;
-            description.dataManagementLabel = dataManagementLabel;
-            Slog.d(TAG, "Transport " + name + " updated its attributes");
-        }
-    }
-
-    @GuardedBy("mTransportLock")
-    private ComponentName getRegisteredTransportComponentOrThrowLocked(String transportName)
-            throws TransportNotRegisteredException {
-        ComponentName transportComponent = getRegisteredTransportComponentLocked(transportName);
-        if (transportComponent == null) {
-            throw new TransportNotRegisteredException(transportName);
-        }
-        return transportComponent;
-    }
-
-    @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            ComponentName transportComponent) throws TransportNotRegisteredException {
-        TransportDescription description =
-                mRegisteredTransportsDescriptionMap.get(transportComponent);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportComponent);
-        }
-        return description;
-    }
-
-    @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            String transportName) throws TransportNotRegisteredException {
-        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportName);
-        }
-        return description;
-    }
-
-    @GuardedBy("mTransportLock")
-    @Nullable
-    private ComponentName getRegisteredTransportComponentLocked(String transportName) {
-        Map.Entry<ComponentName, TransportDescription> entry =
-                getRegisteredTransportEntryLocked(transportName);
-        return (entry == null) ? null : entry.getKey();
-    }
-
-    @GuardedBy("mTransportLock")
-    @Nullable
-    private TransportDescription getRegisteredTransportDescriptionLocked(String transportName) {
-        Map.Entry<ComponentName, TransportDescription> entry =
-                getRegisteredTransportEntryLocked(transportName);
-        return (entry == null) ? null : entry.getValue();
-    }
-
-    @GuardedBy("mTransportLock")
-    @Nullable
-    private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
-            String transportName) {
-        for (Map.Entry<ComponentName, TransportDescription> entry :
-                mRegisteredTransportsDescriptionMap.entrySet()) {
-            TransportDescription description = entry.getValue();
-            if (transportName.equals(description.name)) {
-                return entry;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not
-     * registered.
-     *
-     * @param transportName The name of the transport.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient} or null if not registered.
-     */
-    @Nullable
-    public TransportClient getTransportClient(String transportName, String caller) {
-        try {
-            return getTransportClientOrThrow(transportName, caller);
-        } catch (TransportNotRegisteredException e) {
-            Slog.w(TAG, "Transport " + transportName + " not registered");
-            return null;
-        }
-    }
-
-    /**
-     * Returns a {@link TransportClient} for {@code transportName} or throws if not registered.
-     *
-     * @param transportName The name of the transport.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public TransportClient getTransportClientOrThrow(String transportName, String caller)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            ComponentName component = getRegisteredTransportComponentLocked(transportName);
-            if (component == null) {
-                throw new TransportNotRegisteredException(transportName);
-            }
-            return mTransportClientManager.getTransportClient(component, caller);
-        }
-    }
-
-    /**
-     * Returns a {@link TransportClient} for the current transport or {@code null} if not
-     * registered.
-     *
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient} or null if not registered.
-     * @throws IllegalStateException if no transport is selected.
-     */
-    @Nullable
-    public TransportClient getCurrentTransportClient(String caller) {
-        if (mCurrentTransportName == null) {
-            throw new IllegalStateException("No transport selected");
-        }
-        synchronized (mTransportLock) {
-            return getTransportClient(mCurrentTransportName, caller);
-        }
-    }
-
-    /**
-     * Returns a {@link TransportClient} for the current transport or throws if not registered.
-     *
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     * @throws IllegalStateException if no transport is selected.
-     */
-    public TransportClient getCurrentTransportClientOrThrow(String caller)
-            throws TransportNotRegisteredException {
-        if (mCurrentTransportName == null) {
-            throw new IllegalStateException("No transport selected");
-        }
-        synchronized (mTransportLock) {
-            return getTransportClientOrThrow(mCurrentTransportName, caller);
-        }
-    }
-
-    /**
-     * Disposes of the {@link TransportClient}.
-     *
-     * @param transportClient The {@link TransportClient} to be disposed of.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     */
-    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
-        mTransportClientManager.disposeOfTransportClient(transportClient, caller);
-    }
-
-    /**
-     * Sets {@code transportName} as selected transport and returns previously selected transport
-     * name. If there was no previous transport it returns null.
-     *
-     * <p>You should NOT call this method in new code. This won't make any checks against {@code
-     * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or
-     * another error at the time it's being executed.
-     *
-     * <p>{@link Deprecated} as public, this method can be used as private.
-     */
-    @Deprecated
-    @Nullable
-    String selectTransport(String transportName) {
-        synchronized (mTransportLock) {
-            String prevTransport = mCurrentTransportName;
-            mCurrentTransportName = transportName;
-            return prevTransport;
-        }
-    }
-
-    /**
-     * Tries to register the transport if not registered. If successful also selects the transport.
-     *
-     * @param transportComponent Host of the transport.
-     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
-     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
-     */
-    @WorkerThread
-    public int registerAndSelectTransport(ComponentName transportComponent) {
-        // If it's already registered we select and return
-        synchronized (mTransportLock) {
-            try {
-                selectTransport(getTransportName(transportComponent));
-                return BackupManager.SUCCESS;
-            } catch (TransportNotRegisteredException e) {
-                // Fall through and release lock
-            }
-        }
-
-        // We can't call registerTransport() with the transport lock held
-        int result = registerTransport(transportComponent);
-        if (result != BackupManager.SUCCESS) {
-            return result;
-        }
-        synchronized (mTransportLock) {
-            try {
-                selectTransport(getTransportName(transportComponent));
-                return BackupManager.SUCCESS;
-            } catch (TransportNotRegisteredException e) {
-                Slog.wtf(TAG, "Transport got unregistered");
-                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-            }
-        }
-    }
-
-    @WorkerThread
-    public void registerTransports() {
-        registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true);
-    }
-
-    @WorkerThread
-    private void registerTransportsFromPackage(
-            String packageName, Predicate<ComponentName> transportComponentFilter) {
-        try {
-            mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId);
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.e(TAG, "Trying to register transports from package not found " + packageName);
-            return;
-        }
-
-        registerTransportsForIntent(
-                new Intent(mTransportServiceIntent).setPackage(packageName),
-                transportComponentFilter.and(fromPackageFilter(packageName)));
-    }
-
-    @WorkerThread
-    private void registerTransportsForIntent(
-            Intent intent, Predicate<ComponentName> transportComponentFilter) {
-        List<ResolveInfo> hosts =
-                mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId);
-        if (hosts == null) {
-            return;
-        }
-        for (ResolveInfo host : hosts) {
-            ComponentName transportComponent = host.serviceInfo.getComponentName();
-            if (transportComponentFilter.test(transportComponent)
-                    && isTransportTrusted(transportComponent)) {
-                registerTransport(transportComponent);
-            }
-        }
-    }
-
-    /** Transport has to be whitelisted and privileged. */
-    private boolean isTransportTrusted(ComponentName transport) {
-        if (!mTransportWhitelist.contains(transport)) {
-            Slog.w(
-                    TAG,
-                    "BackupTransport " + transport.flattenToShortString() + " not whitelisted.");
-            return false;
-        }
-        try {
-            PackageInfo packInfo =
-                    mPackageManager.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId);
-            if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                    == 0) {
-                Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
-                return false;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Package not found.", e);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Tries to register transport represented by {@code transportComponent}.
-     *
-     * <p><b>Warning:</b> Don't call this with the transport lock held.
-     *
-     * @param transportComponent Host of the transport that we want to register.
-     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
-     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
-     */
-    @WorkerThread
-    private int registerTransport(ComponentName transportComponent) {
-        checkCanUseTransport();
-
-        if (!isTransportTrusted(transportComponent)) {
-            return BackupManager.ERROR_TRANSPORT_INVALID;
-        }
-
-        String transportString = transportComponent.flattenToShortString();
-        String callerLogString = "TransportManager.registerTransport()";
-
-        Bundle extras = new Bundle();
-        extras.putBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION, true);
-
-        TransportClient transportClient =
-                mTransportClientManager.getTransportClient(
-                        transportComponent, extras, callerLogString);
-        final IBackupTransport transport;
-        try {
-            transport = transportClient.connectOrThrow(callerLogString);
-        } catch (TransportNotAvailableException e) {
-            Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration");
-            mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
-            return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-        }
-
-        int result;
-        try {
-            String transportName = transport.name();
-            String transportDirName = transport.transportDirName();
-            registerTransport(transportComponent, transport);
-            // If registerTransport() hasn't thrown...
-            Slog.d(TAG, "Transport " + transportString + " registered");
-            mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
-            result = BackupManager.SUCCESS;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Transport " + transportString + " died while registering");
-            result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-        }
-
-        mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
-        return result;
-    }
-
-    /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
-    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
-            throws RemoteException {
-        checkCanUseTransport();
-
-        TransportDescription description =
-                new TransportDescription(
-                        transport.name(),
-                        transport.transportDirName(),
-                        transport.configurationIntent(),
-                        transport.currentDestinationString(),
-                        transport.dataManagementIntent(),
-                        transport.dataManagementIntentLabel());
-        synchronized (mTransportLock) {
-            mRegisteredTransportsDescriptionMap.put(transportComponent, description);
-        }
-    }
-
-    private void checkCanUseTransport() {
-        Preconditions.checkState(
-                !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held");
-    }
-
-    public void dumpTransportClients(PrintWriter pw) {
-        mTransportClientManager.dump(pw);
-    }
-
-    public void dumpTransportStats(PrintWriter pw) {
-        mTransportStats.dump(pw);
-    }
-
-    private static Predicate<ComponentName> fromPackageFilter(String packageName) {
-        return transportComponent -> packageName.equals(transportComponent.getPackageName());
-    }
-
-    private static class TransportDescription {
-        private String name;
-        private final String transportDirName;
-        @Nullable private Intent configurationIntent;
-        private String currentDestinationString;
-        @Nullable private Intent dataManagementIntent;
-        @Nullable private CharSequence dataManagementLabel;
-
-        private TransportDescription(
-                String name,
-                String transportDirName,
-                @Nullable Intent configurationIntent,
-                String currentDestinationString,
-                @Nullable Intent dataManagementIntent,
-                @Nullable CharSequence dataManagementLabel) {
-            this.name = name;
-            this.transportDirName = transportDirName;
-            this.configurationIntent = configurationIntent;
-            this.currentDestinationString = currentDestinationString;
-            this.dataManagementIntent = dataManagementIntent;
-            this.dataManagementLabel = dataManagementLabel;
-        }
-    }
-}
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/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
deleted file mode 100644
index a4e9b10..0000000
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ /dev/null
@@ -1,148 +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.backup.transport;
-
-import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
-import static com.android.server.backup.transport.TransportUtils.formatMessage;
-
-import android.annotation.UserIdInt;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.server.backup.TransportManager;
-import com.android.server.backup.transport.TransportUtils.Priority;
-
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
- * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
- */
-public class TransportClientManager {
-    private static final String TAG = "TransportClientManager";
-
-    private final @UserIdInt int mUserId;
-    private final Context mContext;
-    private final TransportStats mTransportStats;
-    private final Object mTransportClientsLock = new Object();
-    private int mTransportClientsCreated = 0;
-    private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
-
-    public TransportClientManager(@UserIdInt int userId, Context context,
-            TransportStats transportStats) {
-        mUserId = userId;
-        mContext = context;
-        mTransportStats = transportStats;
-    }
-
-    /**
-     * Retrieves a {@link TransportClient} for the transport identified by {@param
-     * transportComponent}.
-     *
-     * @param transportComponent The {@link ComponentName} of the transport.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient}.
-     */
-    public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
-        Intent bindIntent =
-                new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
-
-        return getTransportClient(transportComponent, caller, bindIntent);
-    }
-
-    /**
-     * Retrieves a {@link TransportClient} for the transport identified by {@param
-     * transportComponent} whose binding intent will have the {@param extras} extras.
-     *
-     * @param transportComponent The {@link ComponentName} of the transport.
-     * @param extras A {@link Bundle} of extras to pass to the binding intent.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     * @return A {@link TransportClient}.
-     */
-    public TransportClient getTransportClient(
-            ComponentName transportComponent, Bundle extras, String caller) {
-        Intent bindIntent =
-                new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
-        bindIntent.putExtras(extras);
-
-        return getTransportClient(transportComponent, caller, bindIntent);
-    }
-
-    private TransportClient getTransportClient(
-            ComponentName transportComponent, String caller, Intent bindIntent) {
-        synchronized (mTransportClientsLock) {
-            TransportClient transportClient =
-                    new TransportClient(
-                            mUserId,
-                            mContext,
-                            mTransportStats,
-                            bindIntent,
-                            transportComponent,
-                            Integer.toString(mTransportClientsCreated),
-                            caller);
-            mTransportClientsCallerMap.put(transportClient, caller);
-            mTransportClientsCreated++;
-            TransportUtils.log(
-                    Priority.DEBUG,
-                    TAG,
-                    formatMessage(null, caller, "Retrieving " + transportClient));
-            return transportClient;
-        }
-    }
-
-    /**
-     * Disposes of the {@link TransportClient}.
-     *
-     * @param transportClient The {@link TransportClient} to be disposed of.
-     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
-     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
-     *     details.
-     */
-    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
-        transportClient.unbind(caller);
-        transportClient.markAsDisposed();
-        synchronized (mTransportClientsLock) {
-            TransportUtils.log(
-                    Priority.DEBUG,
-                    TAG,
-                    formatMessage(null, caller, "Disposing of " + transportClient));
-            mTransportClientsCallerMap.remove(transportClient);
-        }
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println("Transport clients created: " + mTransportClientsCreated);
-        synchronized (mTransportClientsLock) {
-            pw.println("Current transport clients: " + mTransportClientsCallerMap.size());
-            for (TransportClient transportClient : mTransportClientsCallerMap.keySet()) {
-                String caller = mTransportClientsCallerMap.get(transportClient);
-                pw.println("    " + transportClient + " [" + caller + "]");
-                for (String logEntry : transportClient.getLogBuffer()) {
-                    pw.println("        " + logEntry);
-                }
-            }
-        }
-    }
-}
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/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 79b1226..6bc1a57 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -99,32 +99,28 @@
     public static void scheduleIdlePass(Context context) {
         JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
 
-        final long today3AM = MidnightInTime(0, 0).getTimeInMillis();
-        final long today4AM = MidnightInTime(0, 1).getTimeInMillis();
+        final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis();
+        final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis();
+        final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis();
 
-        long nextScheduleTime, maxScheduleTime;
+        long nextScheduleTime;
         if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) {
             nextScheduleTime = TimeUnit.SECONDS.toMillis(10);
-            maxScheduleTime = today4AM - System.currentTimeMillis();
         } else {
-            final long tomorrow3AM = MidnightInTime(1, 0).getTimeInMillis();
-            final long twodays3AM = MidnightInTime(2, 0).getTimeInMillis();
             nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow
-            maxScheduleTime = twodays3AM - System.currentTimeMillis();   // 3AM in two days
         }
 
         JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
         builder.setRequiresDeviceIdle(true);
-        builder.setRequiresCharging(true);
+        builder.setRequiresBatteryNotLow(true);
         builder.setMinimumLatency(nextScheduleTime);
-        builder.setOverrideDeadline(maxScheduleTime);
         tm.schedule(builder.build());
     }
 
-    private static Calendar MidnightInTime(int nDays, int nHours) {
+    private static Calendar offsetFromTodayMidnight(int nDays, int nHours) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(System.currentTimeMillis());
-        calendar.set(Calendar.HOUR_OF_DAY, 3 + nHours);
+        calendar.set(Calendar.HOUR_OF_DAY, nHours);
         calendar.set(Calendar.MINUTE, 0);
         calendar.set(Calendar.SECOND, 0);
         calendar.set(Calendar.MILLISECOND, 0);
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 3fbb21e..bc50956 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -34,6 +34,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -80,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;
 
@@ -204,7 +209,7 @@
         synchronized (mLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getName());
             if (internalObserver != null) {
-                internalObserver.mRegisteredObserver = observer;
+                internalObserver.registeredObserver = observer;
             }
         }
     }
@@ -222,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) {
@@ -232,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<>();
@@ -307,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())) {
@@ -458,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();
@@ -487,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();
@@ -520,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();
@@ -578,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,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();
             }
         }
@@ -623,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()) {
@@ -677,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) {
@@ -722,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(
@@ -803,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);
         }
 
@@ -826,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);
@@ -843,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);
             }
         }
 
@@ -860,7 +865,7 @@
         @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();
@@ -883,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();
             }
@@ -968,6 +973,9 @@
     class MonitoredPackage {
         //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
@@ -987,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);
@@ -1027,20 +1029,17 @@
          */
         @GuardedBy("mLock")
         public boolean onFailureLocked() {
+            // Sliding window algorithm: find out if there exists a window containing failures >=
+            // mTriggerFailureCount.
             final long now = mSystemClock.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++;
+            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;
         }
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 8367e89..f70d511 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -196,6 +196,9 @@
     private static final String ZRAM_ENABLED_PROPERTY =
             "persist.sys.zram_enabled";
 
+    private static final boolean IS_FUSE_ENABLED =
+            SystemProperties.getBoolean("persist.sys.fuse", false);
+
     private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
 
     /**
@@ -346,6 +349,9 @@
     @GuardedBy("mLock")
     private String mMoveTargetUuid;
 
+    @Nullable
+    private volatile String mMediaStoreAuthorityPackageName = null;
+
     private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     /** Holding lock for AppFuse business */
@@ -1661,6 +1667,15 @@
                 ServiceManager.getService("package"));
         mIAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
+
+        ProviderInfo provider = mPmInternal.resolveContentProvider(
+                MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                UserHandle.getUserId(UserHandle.USER_SYSTEM));
+        if (provider != null) {
+            mMediaStoreAuthorityPackageName = provider.packageName;
+        }
+
         try {
             mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
             mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
@@ -3672,6 +3687,11 @@
                 return Zygote.MOUNT_EXTERNAL_NONE;
             }
 
+            if (IS_FUSE_ENABLED && packageName.equals(mMediaStoreAuthorityPackageName)) {
+                // Determine if caller requires pass_through mount
+                return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+            }
+
             // Determine if caller is holding runtime permission
             final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
                     uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index eb2723a..c412ebd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -284,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
@@ -305,7 +306,7 @@
                     mDefaultSubId = newDefaultSubId;
                     mDefaultPhoneId = newDefaultPhoneId;
                     mLocalLog.log("Default subscription updated: mDefaultPhoneId="
-                            + mDefaultPhoneId + ", mDefaultSubId" + mDefaultSubId);
+                            + mDefaultPhoneId + ", mDefaultSubId=" + mDefaultSubId);
                 }
             }
         }
@@ -335,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));
                 }
             }
         }
@@ -449,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);
     }
@@ -947,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) {
@@ -980,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) {
@@ -1080,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:
@@ -1096,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)) {
@@ -1107,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)) {
@@ -1227,7 +1230,7 @@
     }
 
     public void notifyCellInfoForSubscriber(int subId, List<CellInfo> cellInfo) {
-        if (!checkNotifyPermission("notifyCellInfo()")) {
+        if (!checkNotifyPermission("notifyCellInfoForSubscriber()")) {
             return;
         }
         if (VDBG) {
@@ -1244,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) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 1675b94..08f75e6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1748,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,
@@ -2802,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;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 74157b4..7b69bea 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5265,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");
         }
 
@@ -5317,10 +5320,42 @@
                     });
             mUserController.scheduleStartProfiles();
         }
+        // UART is on if init's console service is running, send a warning notification.
+        showConsoleNotificationIfActive();
 
         t.traceEnd();
     }
 
+    private void showConsoleNotificationIfActive() {
+        if (!SystemProperties.get("init.svc.console").equals("running")) {
+            return;
+        }
+        String title = mContext
+                .getString(com.android.internal.R.string.console_running_notification_title);
+        String message = mContext
+                .getString(com.android.internal.R.string.console_running_notification_message);
+        Notification notification =
+                new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
+                        .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                        .setWhen(0)
+                        .setOngoing(true)
+                        .setTicker(title)
+                        .setDefaults(0)  // please be quiet
+                        .setColor(mContext.getColor(
+                                com.android.internal.R.color
+                                        .system_notification_accent_color))
+                        .setContentTitle(title)
+                        .setContentText(message)
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .build();
+
+        NotificationManager notificationManager =
+                mContext.getSystemService(NotificationManager.class);
+        notificationManager.notifyAsUser(
+                null, SystemMessage.NOTE_SERIAL_CONSOLE_ENABLED, notification, UserHandle.ALL);
+
+    }
+
     @Override
     public void bootAnimationComplete() {
         final boolean callFinishBooting;
@@ -6115,10 +6150,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);
     }
 
     /**
@@ -7088,9 +7122,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...
@@ -15264,7 +15299,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.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+                                } else {
+                                    mAppOpsService.uidRemoved(uid);
+                                }
                             }
                             break;
                         case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index d46c626..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;
     }
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 56208a95..59c2326 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -446,7 +446,9 @@
         mHandler.removeCallbacksAndMessages(msgToken);
         // ...then schedule the removal of the token after the extended timeout
         mHandler.postAtTime(() -> {
-            app.removeAllowBackgroundActivityStartsToken(r);
+            synchronized (mService) {
+                app.removeAllowBackgroundActivityStartsToken(r);
+            }
         }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
     }
 
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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7f69a68..f866314 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -608,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();
             }
@@ -803,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);
             }
@@ -882,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);
                     }
                 }
@@ -2852,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,
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 09f5286..96af74a 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -79,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;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..33d8dec 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -18,8 +18,11 @@
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 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,29 +37,69 @@
     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 void reportChangeByPackageName(long changeId, String packageName) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName);
+        if (appInfo == null) {
+            return;
+        }
+        reportChange(changeId, appInfo);
     }
 
     @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;
     }
 
     @Override
+    public boolean isChangeEnabledByPackageName(long changeId, String packageName) {
+        ApplicationInfo appInfo = getApplicationInfo(packageName);
+        if (appInfo == null) {
+            return true;
+        }
+        return isChangeEnabled(changeId, appInfo);
+    }
+
+    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
     }
+
+    private ApplicationInfo getApplicationInfo(String packageName) {
+        try {
+            return mContext.getPackageManager().getApplicationInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "No installed package " + packageName);
+        }
+        return null;
+    }
+
+    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/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/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 1fc0db3..d24bd1a 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -49,8 +49,9 @@
 
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.R;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterFactory;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
-import com.android.server.display.whitebalance.AmbientFilter;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -970,7 +971,7 @@
                 if (lightSensor != null) {
                     final Resources res = mContext.getResources();
 
-                    mAmbientFilter = DisplayWhiteBalanceFactory.createBrightnessFilter(res);
+                    mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
                     mLightSensor = lightSensor;
 
                     onScreenOn(isDefaultDisplayOn());
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/utils/AmbientFilter.java b/services/core/java/com/android/server/display/utils/AmbientFilter.java
new file mode 100644
index 0000000..1a84121
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/AmbientFilter.java
@@ -0,0 +1,254 @@
+/*
+ * 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.display.utils;
+
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/**
+ * The DisplayWhiteBalanceController uses the AmbientFilter to average ambient changes over time,
+ * filter out the noise, and arrive at an estimate of the actual value.
+ *
+ * When the DisplayWhiteBalanceController detects a change in ambient brightness or color
+ * temperature, it passes it to the AmbientFilter, and when it needs the actual ambient value, it
+ * asks it for an estimate.
+ *
+ * Implementations:
+ * - {@link WeightedMovingAverageAmbientFilter}
+ *   A weighted average prioritising recent changes.
+ */
+abstract public class AmbientFilter {
+
+    protected static final boolean DEBUG = false; // Enable for verbose logs.
+
+    protected final String mTag;
+    protected boolean mLoggingEnabled;
+
+    // How long ambient value changes are kept and taken into consideration.
+    private final int mHorizon; // Milliseconds
+
+    private final RollingBuffer mBuffer;
+
+    /**
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param horizon
+     *      How long ambient value changes are kept and taken into consideration.
+     *
+     * @throws IllegalArgumentException
+     *      - horizon is not positive.
+     */
+    AmbientFilter(String tag, int horizon) {
+        validateArguments(horizon);
+        mTag = tag;
+        mLoggingEnabled = false;
+        mHorizon = horizon;
+        mBuffer = new RollingBuffer();
+    }
+
+    /**
+     * Add an ambient value change.
+     *
+     * @param time
+     *      The time.
+     * @param value
+     *      The ambient value.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean addValue(long time, float value) {
+        if (value < 0.0f) {
+            return false;
+        }
+        truncateOldValues(time);
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "add value: " + value + " @ " + time);
+        }
+        mBuffer.add(time, value);
+        return true;
+    }
+
+    /**
+     * Get an estimate of the actual ambient color temperature.
+     *
+     * @param time
+     *      The time.
+     *
+     * @return An estimate of the actual ambient color temperature.
+     */
+    public float getEstimate(long time) {
+        truncateOldValues(time);
+        final float value = filter(time, mBuffer);
+        if (mLoggingEnabled) {
+            Slog.d(mTag, "get estimate: " + value + " @ " + time);
+        }
+        return value;
+    }
+
+    /**
+     * Clears the filter state.
+     */
+    public void clear() {
+        mBuffer.clear();
+    }
+
+    /**
+     * Enable/disable logging.
+     *
+     * @param loggingEnabled
+     *      Whether logging is on/off.
+     *
+     * @return Whether the method succeeded or not.
+     */
+    public boolean setLoggingEnabled(boolean loggingEnabled) {
+        if (mLoggingEnabled == loggingEnabled) {
+            return false;
+        }
+        mLoggingEnabled = loggingEnabled;
+        return true;
+    }
+
+    /**
+     * Dump the state.
+     *
+     * @param writer
+     *      The PrintWriter used to dump the state.
+     */
+    public void dump(PrintWriter writer) {
+        writer.println("  " + mTag);
+        writer.println("    mLoggingEnabled=" + mLoggingEnabled);
+        writer.println("    mHorizon=" + mHorizon);
+        writer.println("    mBuffer=" + mBuffer);
+    }
+
+    private void validateArguments(int horizon) {
+        if (horizon <= 0) {
+            throw new IllegalArgumentException("horizon must be positive");
+        }
+    }
+
+    private void truncateOldValues(long time) {
+        final long minTime = time - mHorizon;
+        mBuffer.truncate(minTime);
+    }
+
+    protected abstract float filter(long time, RollingBuffer buffer);
+
+    /**
+     * A weighted average prioritising recent changes.
+     */
+    static class WeightedMovingAverageAmbientFilter extends AmbientFilter {
+
+        // How long the latest ambient value change is predicted to last.
+        private static final int PREDICTION_TIME = 100; // Milliseconds
+
+        // Recent changes are prioritised by integrating their duration over y = x + mIntercept
+        // (the higher it is, the less prioritised recent changes are).
+        private final float mIntercept;
+
+        /**
+         * @param tag
+         *      The tag used for dumping and logging.
+         * @param horizon
+         *      How long ambient value changes are kept and taken into consideration.
+         * @param intercept
+         *      Recent changes are prioritised by integrating their duration over y = x + intercept
+         *      (the higher it is, the less prioritised recent changes are).
+         *
+         * @throws IllegalArgumentException
+         *      - horizon is not positive.
+         *      - intercept is NaN or negative.
+         */
+        WeightedMovingAverageAmbientFilter(String tag, int horizon, float intercept) {
+            super(tag, horizon);
+            validateArguments(intercept);
+            mIntercept = intercept;
+        }
+
+        /**
+         * See {@link AmbientFilter#dump base class}.
+         */
+        @Override
+        public void dump(PrintWriter writer) {
+            super.dump(writer);
+            writer.println("    mIntercept=" + mIntercept);
+        }
+
+        // Normalise the times to [t1=0, t2, ..., tN, now + PREDICTION_TIME], so the first change
+        // starts at 0 and the last change is predicted to last a bit, and divide them by 1000 as
+        // milliseconds are high enough to overflow.
+        // The weight of the value from t[i] to t[i+1] is the area under (A.K.A. the integral of)
+        // y = x + mIntercept from t[i] to t[i+1].
+        @Override
+        protected float filter(long time, RollingBuffer buffer) {
+            if (buffer.isEmpty()) {
+                return -1.0f;
+            }
+            float total = 0.0f;
+            float totalWeight = 0.0f;
+            final float[] weights = getWeights(time, buffer);
+            if (DEBUG && mLoggingEnabled) {
+                Slog.v(mTag, "filter: " + buffer + " => " + Arrays.toString(weights));
+            }
+            for (int i = 0; i < weights.length; i++) {
+                final float value = buffer.getValue(i);
+                final float weight = weights[i];
+                total += weight * value;
+                totalWeight += weight;
+            }
+            if (totalWeight == 0.0f) {
+                return buffer.getValue(buffer.size() - 1);
+            }
+            return total / totalWeight;
+        }
+
+        private void validateArguments(float intercept) {
+            if (Float.isNaN(intercept) || intercept < 0.0f) {
+                throw new IllegalArgumentException("intercept must be a non-negative number");
+            }
+        }
+
+        private float[] getWeights(long time, RollingBuffer buffer) {
+            float[] weights = new float[buffer.size()];
+            final long startTime = buffer.getTime(0);
+            float previousTime = 0.0f;
+            for (int i = 1; i < weights.length; i++) {
+                final float currentTime = (buffer.getTime(i) - startTime) / 1000.0f;
+                final float weight = calculateIntegral(previousTime, currentTime);
+                weights[i - 1] = weight;
+                previousTime = currentTime;
+            }
+            final float lastTime = (time + PREDICTION_TIME - startTime) / 1000.0f;
+            final float lastWeight = calculateIntegral(previousTime, lastTime);
+            weights[weights.length - 1] = lastWeight;
+            return weights;
+        }
+
+        private float calculateIntegral(float from, float to) {
+            return antiderivative(to) - antiderivative(from);
+        }
+
+        private float antiderivative(float x) {
+            // f(x) = x + c => F(x) = 1/2 * x^2 + c * x
+            return 0.5f * x * x + mIntercept * x;
+        }
+
+    }
+
+}
diff --git a/services/core/java/com/android/server/display/utils/AmbientFilterFactory.java b/services/core/java/com/android/server/display/utils/AmbientFilterFactory.java
new file mode 100644
index 0000000..dfa1ddc
--- /dev/null
+++ b/services/core/java/com/android/server/display/utils/AmbientFilterFactory.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.server.display.utils;
+
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.TypedValue;
+
+public class AmbientFilterFactory {
+    /**
+     * Creates a temporal filter which functions as a weighted moving average buffer for recent
+     * sensor values.
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param horizon
+     *      How long ambient value changes are kept and taken into consideration.
+     * @param intercept
+     *      Recent changes are prioritised by integrating their duration over y = x + intercept
+     *      (the higher it is, the less prioritised recent changes are).
+     *
+     * @return
+     *      An AmbientFiler.
+     *
+     * @throws IllegalArgumentException
+     *      - Horizon is not positive.
+     *      - Intercept not configured.
+     */
+    public static AmbientFilter createAmbientFilter(String tag, int horizon, float intercept) {
+        if (!Float.isNaN(intercept)) {
+            return new AmbientFilter.WeightedMovingAverageAmbientFilter(tag, horizon, intercept);
+        }
+        throw new IllegalArgumentException("missing configurations: "
+                + "expected config_displayWhiteBalanceBrightnessFilterIntercept");
+    }
+
+    /**
+     * Helper to create a default BrightnessFilter which has configuration in the resource file.
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param resources
+     *      The resources used to configure the various components.
+     *
+     * @return
+     *      An AmbientFilter.
+     */
+    public static AmbientFilter createBrightnessFilter(String tag, Resources resources) {
+        final int horizon = resources.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon);
+        final float intercept = getFloat(resources,
+                com.android.internal.R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept);
+
+        return createAmbientFilter(tag, horizon, intercept);
+    }
+
+    /**
+     * Helper to creates a default ColorTemperatureFilter which has configuration in the resource
+     * file.
+     * @param tag
+     *      The tag used for dumping and logging.
+     * @param resources
+     *      The resources used to configure the various components.
+     *
+     * @return
+     *      An AmbientFilter.
+     */
+    public static AmbientFilter createColorTemperatureFilter(String tag, Resources resources) {
+        final int horizon = resources.getInteger(
+                com.android.internal.R.integer
+                .config_displayWhiteBalanceColorTemperatureFilterHorizon);
+        final float intercept = getFloat(resources,
+                com.android.internal.R.dimen
+                .config_displayWhiteBalanceColorTemperatureFilterIntercept);
+
+        return createAmbientFilter(tag, horizon, intercept);
+    }
+
+    // Instantiation is disabled.
+    private AmbientFilterFactory() { }
+
+    private static float getFloat(Resources resources, int id) {
+        TypedValue value = new TypedValue();
+
+        resources.getValue(id, value, true /* resolveRefs */);
+        if (value.type != TypedValue.TYPE_FLOAT) {
+            return Float.NaN;
+        }
+
+        return value.getFloat();
+    }
+}
+
diff --git a/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java b/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java
deleted file mode 100644
index 3580897..0000000
--- a/services/core/java/com/android/server/display/whitebalance/AmbientFilter.java
+++ /dev/null
@@ -1,257 +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.display.whitebalance;
-
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.utils.RollingBuffer;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-/**
- * The DisplayWhiteBalanceController uses the AmbientFilter to average ambient changes over time,
- * filter out the noise, and arrive at an estimate of the actual value.
- *
- * When the DisplayWhiteBalanceController detects a change in ambient brightness or color
- * temperature, it passes it to the AmbientFilter, and when it needs the actual ambient value, it
- * asks it for an estimate.
- *
- * Implementations:
- * - {@link WeightedMovingAverageAmbientFilter}
- *   A weighted average prioritising recent changes.
- */
-abstract public class AmbientFilter {
-
-    protected static final boolean DEBUG = false; // Enable for verbose logs.
-
-    protected final String mTag;
-    protected boolean mLoggingEnabled;
-
-    // How long ambient value changes are kept and taken into consideration.
-    private final int mHorizon; // Milliseconds
-
-    private final RollingBuffer mBuffer;
-
-    /**
-     * @param tag
-     *      The tag used for dumping and logging.
-     * @param horizon
-     *      How long ambient value changes are kept and taken into consideration.
-     *
-     * @throws IllegalArgumentException
-     *      - horizon is not positive.
-     */
-    AmbientFilter(String tag, int horizon) {
-        validateArguments(horizon);
-        mTag = tag;
-        mLoggingEnabled = false;
-        mHorizon = horizon;
-        mBuffer = new RollingBuffer();
-    }
-
-    /**
-     * Add an ambient value change.
-     *
-     * @param time
-     *      The time.
-     * @param value
-     *      The ambient value.
-     *
-     * @return Whether the method succeeded or not.
-     */
-    public boolean addValue(long time, float value) {
-        if (value < 0.0f) {
-            return false;
-        }
-        truncateOldValues(time);
-        if (mLoggingEnabled) {
-            Slog.d(mTag, "add value: " + value + " @ " + time);
-        }
-        mBuffer.add(time, value);
-        return true;
-    }
-
-    /**
-     * Get an estimate of the actual ambient color temperature.
-     *
-     * @param time
-     *      The time.
-     *
-     * @return An estimate of the actual ambient color temperature.
-     */
-    public float getEstimate(long time) {
-        truncateOldValues(time);
-        final float value = filter(time, mBuffer);
-        if (mLoggingEnabled) {
-            Slog.d(mTag, "get estimate: " + value + " @ " + time);
-        }
-        return value;
-    }
-
-    /**
-     * Clears the filter state.
-     */
-    public void clear() {
-        mBuffer.clear();
-    }
-
-    /**
-     * Enable/disable logging.
-     *
-     * @param loggingEnabled
-     *      Whether logging is on/off.
-     *
-     * @return Whether the method succeeded or not.
-     */
-    public boolean setLoggingEnabled(boolean loggingEnabled) {
-        if (mLoggingEnabled == loggingEnabled) {
-            return false;
-        }
-        mLoggingEnabled = loggingEnabled;
-        return true;
-    }
-
-    /**
-     * Dump the state.
-     *
-     * @param writer
-     *      The PrintWriter used to dump the state.
-     */
-    public void dump(PrintWriter writer) {
-        writer.println("  " + mTag);
-        writer.println("    mLoggingEnabled=" + mLoggingEnabled);
-        writer.println("    mHorizon=" + mHorizon);
-        writer.println("    mBuffer=" + mBuffer);
-    }
-
-    private void validateArguments(int horizon) {
-        if (horizon <= 0) {
-            throw new IllegalArgumentException("horizon must be positive");
-        }
-    }
-
-    private void truncateOldValues(long time) {
-        final long minTime = time - mHorizon;
-        mBuffer.truncate(minTime);
-    }
-
-    protected abstract float filter(long time, RollingBuffer buffer);
-
-    /**
-     * A weighted average prioritising recent changes.
-     */
-    static class WeightedMovingAverageAmbientFilter extends AmbientFilter {
-
-        // How long the latest ambient value change is predicted to last.
-        private static final int PREDICTION_TIME = 100; // Milliseconds
-
-        // Recent changes are prioritised by integrating their duration over y = x + mIntercept
-        // (the higher it is, the less prioritised recent changes are).
-        private final float mIntercept;
-
-        /**
-         * @param tag
-         *      The tag used for dumping and logging.
-         * @param horizon
-         *      How long ambient value changes are kept and taken into consideration.
-         * @param intercept
-         *      Recent changes are prioritised by integrating their duration over y = x + intercept
-         *      (the higher it is, the less prioritised recent changes are).
-         *
-         * @throws IllegalArgumentException
-         *      - horizon is not positive.
-         *      - intercept is NaN or negative.
-         */
-        WeightedMovingAverageAmbientFilter(String tag, int horizon, float intercept) {
-            super(tag, horizon);
-            validateArguments(intercept);
-            mIntercept = intercept;
-        }
-
-        /**
-         * See {@link AmbientFilter#dump base class}.
-         */
-        @Override
-        public void dump(PrintWriter writer) {
-            super.dump(writer);
-            writer.println("    mIntercept=" + mIntercept);
-        }
-
-        // Normalise the times to [t1=0, t2, ..., tN, now + PREDICTION_TIME], so the first change
-        // starts at 0 and the last change is predicted to last a bit, and divide them by 1000 as
-        // milliseconds are high enough to overflow.
-        // The weight of the value from t[i] to t[i+1] is the area under (A.K.A. the integral of)
-        // y = x + mIntercept from t[i] to t[i+1].
-        @Override
-        protected float filter(long time, RollingBuffer buffer) {
-            if (buffer.isEmpty()) {
-                return -1.0f;
-            }
-            float total = 0.0f;
-            float totalWeight = 0.0f;
-            final float[] weights = getWeights(time, buffer);
-            if (DEBUG && mLoggingEnabled) {
-                Slog.v(mTag, "filter: " + buffer + " => " + Arrays.toString(weights));
-            }
-            for (int i = 0; i < weights.length; i++) {
-                final float value = buffer.getValue(i);
-                final float weight = weights[i];
-                total += weight * value;
-                totalWeight += weight;
-            }
-            if (totalWeight == 0.0f) {
-                return buffer.getValue(buffer.size() - 1);
-            }
-            return total / totalWeight;
-        }
-
-        private void validateArguments(float intercept) {
-            if (Float.isNaN(intercept) || intercept < 0.0f) {
-                throw new IllegalArgumentException("intercept must be a non-negative number");
-            }
-        }
-
-        private float[] getWeights(long time, RollingBuffer buffer) {
-            float[] weights = new float[buffer.size()];
-            final long startTime = buffer.getTime(0);
-            float previousTime = 0.0f;
-            for (int i = 1; i < weights.length; i++) {
-                final float currentTime = (buffer.getTime(i) - startTime) / 1000.0f;
-                final float weight = calculateIntegral(previousTime, currentTime);
-                weights[i - 1] = weight;
-                previousTime = currentTime;
-            }
-            final float lastTime = (time + PREDICTION_TIME - startTime) / 1000.0f;
-            final float lastWeight = calculateIntegral(previousTime, lastTime);
-            weights[weights.length - 1] = lastWeight;
-            return weights;
-        }
-
-        private float calculateIntegral(float from, float to) {
-            return antiderivative(to) - antiderivative(from);
-        }
-
-        private float antiderivative(float x) {
-            // f(x) = x + c => F(x) = 1/2 * x^2 + c * x
-            return 0.5f * x * x + mIntercept * x;
-        }
-
-    }
-
-}
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..e8a5779 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -24,6 +24,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.History;
 
 import java.io.PrintWriter;
@@ -95,6 +96,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 +354,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 +362,7 @@
         }
 
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
+        mLatestAmbientBrightness = ambientBrightness;
 
         if (ambientColorTemperature != -1.0f &&
                 mLowLightAmbientBrightnessToBiasSpline != null) {
@@ -362,6 +370,7 @@
             ambientColorTemperature =
                     bias * ambientColorTemperature + (1.0f - bias)
                     * mLowLightAmbientColorTemperature;
+            mLatestLowLightBias = bias;
         }
         if (ambientColorTemperature != -1.0f &&
                 mHighLightAmbientBrightnessToBiasSpline != null) {
@@ -369,6 +378,7 @@
             ambientColorTemperature =
                     (1.0f - bias) * ambientColorTemperature + bias
                     * mHighLightAmbientColorTemperature;
+            mLatestHighLightBias = bias;
         }
 
         if (mAmbientColorTemperatureOverride != -1.0f) {
@@ -426,6 +436,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/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
index bf0a1d1..a72b1ed 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java
@@ -23,6 +23,8 @@
 import android.util.TypedValue;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterFactory;
 
 /**
  * The DisplayWhiteBalanceFactory creates and configures an DisplayWhiteBalanceController.
@@ -58,10 +60,12 @@
             SensorManager sensorManager, Resources resources) {
         final AmbientSensor.AmbientBrightnessSensor brightnessSensor =
                 createBrightnessSensor(handler, sensorManager, resources);
-        final AmbientFilter brightnessFilter = createBrightnessFilter(resources);
+        final AmbientFilter brightnessFilter =
+                AmbientFilterFactory.createBrightnessFilter(BRIGHTNESS_FILTER_TAG, resources);
         final AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor =
                 createColorTemperatureSensor(handler, sensorManager, resources);
-        final AmbientFilter colorTemperatureFilter = createColorTemperatureFilter(resources);
+        final AmbientFilter colorTemperatureFilter = AmbientFilterFactory
+                .createColorTemperatureFilter(COLOR_TEMPERATURE_FILTER_TAG, resources);
         final DisplayWhiteBalanceThrottler throttler = createThrottler(resources);
         final float[] displayWhiteBalanceLowLightAmbientBrightnesses = getFloatArray(resources,
                 com.android.internal.R.array
@@ -112,23 +116,6 @@
     }
 
     /**
-     * Creates a BrightnessFilter which functions as a weighted moving average buffer for recent
-     * brightness values.
-     */
-    public static AmbientFilter createBrightnessFilter(Resources resources) {
-        final int horizon = resources.getInteger(
-                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon);
-        final float intercept = getFloat(resources,
-                com.android.internal.R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept);
-        if (!Float.isNaN(intercept)) {
-            return new AmbientFilter.WeightedMovingAverageAmbientFilter(
-                    BRIGHTNESS_FILTER_TAG, horizon, intercept);
-        }
-        throw new IllegalArgumentException("missing configurations: "
-                + "expected config_displayWhiteBalanceBrightnessFilterIntercept");
-    }
-
-    /**
      * Creates an ambient color sensor instance to redirect sensor data to callbacks.
      */
     @VisibleForTesting
@@ -143,21 +130,6 @@
         return new AmbientSensor.AmbientColorTemperatureSensor(handler, sensorManager, name, rate);
     }
 
-    private static AmbientFilter createColorTemperatureFilter(Resources resources) {
-        final int horizon = resources.getInteger(
-                com.android.internal.R.integer
-                .config_displayWhiteBalanceColorTemperatureFilterHorizon);
-        final float intercept = getFloat(resources,
-                com.android.internal.R.dimen
-                .config_displayWhiteBalanceColorTemperatureFilterIntercept);
-        if (!Float.isNaN(intercept)) {
-            return new AmbientFilter.WeightedMovingAverageAmbientFilter(
-                    COLOR_TEMPERATURE_FILTER_TAG, horizon, intercept);
-        }
-        throw new IllegalArgumentException("missing configurations: "
-                + "expected config_displayWhiteBalanceColorTemperatureFilterIntercept");
-    }
-
     private static DisplayWhiteBalanceThrottler createThrottler(Resources resources) {
         final int increaseDebounce = resources.getInteger(
                 com.android.internal.R.integer.config_displayWhiteBalanceDecreaseDebounce);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2b849d6..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 =
@@ -581,6 +587,7 @@
         }
         if (reason != -1) {
             invokeVendorCommandListenersOnControlStateChanged(true, reason);
+            announceHdmiControlStatusChange(true);
         }
     }
 
@@ -1370,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;
 
@@ -1695,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);
@@ -2218,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 {
@@ -2450,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);
     }
@@ -2819,6 +2957,8 @@
                 disableHdmiControlService();
             }
         });
+        announceHdmiControlStatusChange(enabled);
+
         return;
     }
 
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/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 f71b362..d480cb6e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -356,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;
@@ -524,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());
                 }
             }
         }
@@ -549,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();
@@ -656,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) {
@@ -1363,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)) {
@@ -1741,7 +1778,6 @@
 
         mPolicyFile = policyFile;
         loadPolicyFile();
-
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         if (mStatusBar != null) {
             mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -2957,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);
@@ -2981,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();
         }
 
@@ -5469,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.
 
@@ -5676,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);
@@ -5775,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: "
@@ -5807,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));
@@ -5821,6 +5906,7 @@
             }
             return true;
         }
+
         if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5828,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) {
@@ -5861,7 +5957,6 @@
         } catch (Exception e) {
             Slog.w(TAG, "error recovering builder", e);
         }
-
         return false;
     }
 
@@ -6056,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)
@@ -6420,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);
             }
@@ -7578,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/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/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 b124c4b..d07e2d2 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;
@@ -137,7 +137,6 @@
 import android.content.IntentSender.SendIntentException;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.AppsQueryHelper;
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.ChangedPackages;
 import android.content.pm.ComponentInfo;
@@ -581,16 +580,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 {
@@ -756,6 +745,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.
@@ -1582,7 +1591,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;
@@ -2463,54 +2472,21 @@
         PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore);
         t.traceEnd(); // "create package manager"
 
-        m.enableSystemUserPackages();
+        m.installWhitelistedSystemPackages();
         ServiceManager.addService("package", m);
         final PackageManagerNative pmn = m.new PackageManagerNative();
         ServiceManager.addService("package_native", pmn);
         return m;
     }
 
-    private void enableSystemUserPackages() {
-        if (!UserManager.isSplitSystemUser()) {
-            return;
-        }
-        // For system user, enable apps based on the following conditions:
-        // - app is whitelisted or belong to one of these groups:
-        //   -- system app which has no launcher icons
-        //   -- system app which has INTERACT_ACROSS_USERS permission
-        //   -- system IME app
-        // - app is not in the blacklist
-        AppsQueryHelper queryHelper = new AppsQueryHelper(this);
-        Set<String> enableApps = new ArraySet<>();
-        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_NON_LAUNCHABLE_APPS
-                | AppsQueryHelper.GET_APPS_WITH_INTERACT_ACROSS_USERS_PERM
-                | AppsQueryHelper.GET_IMES, /* systemAppsOnly */ true, UserHandle.SYSTEM));
-        ArraySet<String> wlApps = SystemConfig.getInstance().getSystemUserWhitelistedApps();
-        enableApps.addAll(wlApps);
-        enableApps.addAll(queryHelper.queryApps(AppsQueryHelper.GET_REQUIRED_FOR_SYSTEM_USER,
-                /* systemAppsOnly */ false, UserHandle.SYSTEM));
-        ArraySet<String> blApps = SystemConfig.getInstance().getSystemUserBlacklistedApps();
-        enableApps.removeAll(blApps);
-        Log.i(TAG, "Applications installed for system user: " + enableApps);
-        List<String> allAps = queryHelper.queryApps(0, /* systemAppsOnly */ false,
-                UserHandle.SYSTEM);
-        final int allAppsSize = allAps.size();
+    /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+    private void installWhitelistedSystemPackages() {
         synchronized (mLock) {
-            for (int i = 0; i < allAppsSize; i++) {
-                String pName = allAps.get(i);
-                PackageSetting pkgSetting = mSettings.mPackages.get(pName);
-                // Should not happen, but we shouldn't be failing if it does
-                if (pkgSetting == null) {
-                    continue;
-                }
-                boolean install = enableApps.contains(pName);
-                if (pkgSetting.getInstalled(UserHandle.USER_SYSTEM) != install) {
-                    Log.i(TAG, (install ? "Installing " : "Uninstalling ") + pName
-                            + " for system user");
-                    pkgSetting.setInstalled(install, UserHandle.USER_SYSTEM);
-                }
+            final boolean scheduleWrite = mUserManager.installWhitelistedSystemPackages(
+                    isFirstBoot(), isDeviceUpgrading());
+            if (scheduleWrite) {
+                scheduleWritePackageRestrictionsLocked(UserHandle.USER_ALL);
             }
-            scheduleWritePackageRestrictionsLocked(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -2552,6 +2528,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);
@@ -2775,215 +2796,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<>();
@@ -3151,89 +2992,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 {
@@ -8375,15 +8153,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);
             }
@@ -17124,8 +16907,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);
                     }
@@ -17930,8 +17712,14 @@
                 }
             }
             if (removedAppId >= 0) {
+                // If a system app's updates are uninstalled the UID is not actually removed. Some
+                // services need to know the package name affected.
+                if (extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+                    extras.putString(Intent.EXTRA_PACKAGE_NAME, removedPackage);
+                }
+
                 packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
-                    null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+                        null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                     null, null, broadcastUsers, instantUserIds);
             }
         }
@@ -18080,70 +17868,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.
@@ -18254,23 +17987,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);
@@ -19787,7 +19512,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);
@@ -22559,10 +22284,19 @@
         }
     }
 
-    /** Called by UserManagerService */
-    void createNewUser(int userId, String[] disallowedPackages) {
+    /**
+     * Called by UserManagerService.
+     *
+     * @param installablePackages system packages that should be initially installed for this user,
+     *                            or {@code null} if all system packages should be installed
+     * @param disallowedPackages packages that should not be initially installed. Takes precedence
+     *                           over installablePackages.
+     */
+    void createNewUser(int userId, @Nullable Set<String> installablePackages,
+            String[] disallowedPackages) {
         synchronized (mInstallLock) {
-            mSettings.createNewUserLI(this, mInstaller, userId, disallowedPackages);
+            mSettings.createNewUserLI(this, mInstaller, userId,
+                    installablePackages, disallowedPackages);
         }
         synchronized (mLock) {
             scheduleWritePackageRestrictionsLocked(userId);
@@ -23371,6 +23105,19 @@
         }
 
         @Override
+        public boolean setInstalled(PackageParser.Package pkg, @UserIdInt int userId,
+                boolean installed) {
+            synchronized (mLock) {
+                final PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
+                if (ps.getInstalled(userId) != installed) {
+                    ps.setInstalled(installed, userId);
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        @Override
         public void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
                 Intent origIntent, String resolvedType, String callingPackage,
                 Bundle verificationBundle, int userId) {
@@ -23380,11 +23127,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 fe529a1..3f32f3d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -496,6 +496,10 @@
             getErrPrintWriter().println("Error: no package specified");
             return 1;
         }
+        userId = translateUserId(userId, true /*allowAll*/, "runPath");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         return displayPackageFilePath(pkg, userId);
     }
 
@@ -718,6 +722,10 @@
 
         final String filter = getNextArg();
 
+        userId = translateUserId(userId, true /*allowAll*/, "runListPackages");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         @SuppressWarnings("unchecked")
         final ParceledListSlice<PackageInfo> slice =
                 mInterface.getInstalledPackages(getFlags, userId);
@@ -1285,7 +1293,7 @@
 
     private int runInstallExisting() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        int userId = UserHandle.USER_SYSTEM;
+        int userId = UserHandle.USER_CURRENT;
         int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
         String opt;
         boolean waitTillComplete = false;
@@ -1320,6 +1328,10 @@
             pw.println("Error: package name not specified");
             return 1;
         }
+        userId = translateUserId(userId, true /*allowAll*/, "runInstallExisting");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
 
         int installReason = PackageManager.INSTALL_REASON_UNKNOWN;
         try {
@@ -1945,6 +1957,10 @@
             getErrPrintWriter().println("Error: no package or component specified");
             return 1;
         }
+        userId = translateUserId(userId, true /*allowAll*/, "runSetEnabledSetting");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         ComponentName cn = ComponentName.unflattenFromString(pkg);
         if (cn == null) {
             mInterface.setApplicationEnabledSetting(pkg, state, 0, userId,
@@ -1974,6 +1990,10 @@
             getErrPrintWriter().println("Error: no package or component specified");
             return 1;
         }
+        userId = translateUserId(userId, true /*allowAll*/, "runSetHiddenSetting");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId);
         getOutPrintWriter().println("Package " + pkg + " new hidden state: "
                 + mInterface.getApplicationHiddenSettingAsUser(pkg, userId));
@@ -2043,6 +2063,10 @@
             info = null;
         }
         try {
+            userId = translateUserId(userId, true /*allowAll*/, "runSuspend");
+            if (userId == UserHandle.USER_ALL) {
+                userId = UserHandle.USER_SYSTEM;
+            }
             mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
                     appExtras, launcherExtras, info, callingPackage, userId);
             pw.println("Package " + packageName + " new suspended state: "
@@ -2074,7 +2098,7 @@
             getErrPrintWriter().println("Error: no permission specified");
             return 1;
         }
-
+        userId = translateUserId(userId, true /*allowAll*/, "runGrantRevokePermission");
         if (grant) {
             mPermissionManager.grantRuntimePermission(pkg, perm, userId);
         } else {
@@ -2262,6 +2286,10 @@
                 return 1;
         }
 
+        userId = translateUserId(userId, true /*allowAll*/, "runSetAppLink");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
         if (info == null) {
             getErrPrintWriter().println("Error: package " + pkg + " not found.");
@@ -2302,6 +2330,10 @@
             return 1;
         }
 
+        userId = translateUserId(userId, true /*allowAll*/, "runGetAppLink");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
         if (info == null) {
             getErrPrintWriter().println("Error: package " + pkg + " not found.");
@@ -2666,8 +2698,7 @@
             }
             pkgName = componentName.getPackageName();
         }
-
-
+        userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
         final CompletableFuture<Boolean> future = new CompletableFuture<>();
         final RemoteCallback callback = new RemoteCallback(res -> future.complete(res != null));
         try {
@@ -2763,8 +2794,10 @@
             }
         }
 
-        userId = translateUserId(userId, false /*allowAll*/, "runSetHarmfulAppWarning");
-
+        userId = translateUserId(userId, true /*allowAll*/, "runSetHarmfulAppWarning");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         final String packageName = getNextArgRequired();
         final String warning = getNextArg();
 
@@ -2786,8 +2819,10 @@
             }
         }
 
-        userId = translateUserId(userId, false /*allowAll*/, "runGetHarmfulAppWarning");
-
+        userId = translateUserId(userId, true /*allowAll*/, "runGetHarmfulAppWarning");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+        }
         final String packageName = getNextArgRequired();
         final CharSequence warning = mInterface.getHarmfulAppWarning(packageName, userId);
         if (!TextUtils.isEmpty(warning)) {
@@ -2824,7 +2859,7 @@
 
     private int doCreateSession(SessionParams params, String installerPackageName, int userId)
             throws RemoteException {
-        userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
+        userId = translateUserId(userId, true /*allowAll*/, "doCreateSession");
         if (userId == UserHandle.USER_ALL) {
             userId = UserHandle.USER_SYSTEM;
             params.installFlags |= PackageManager.INSTALL_ALL_USERS;
@@ -3115,13 +3150,13 @@
         pw.println("  dump PACKAGE");
         pw.println("    Print various system state associated with the given PACKAGE.");
         pw.println("");
-        pw.println("  list features");
-        pw.println("    Prints all features of the system.");
-        pw.println("");
         pw.println("  has-feature FEATURE_NAME [version]");
         pw.println("    Prints true and returns exit status 0 when system has a FEATURE_NAME,");
         pw.println("    otherwise prints false and returns exit status 1");
         pw.println("");
+        pw.println("  list features");
+        pw.println("    Prints all features of the system.");
+        pw.println("");
         pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
         pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
         pw.println("    Options:");
@@ -3161,11 +3196,14 @@
         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("    Prints all staged sessions.");
         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("  list users");
+        pw.println("    Prints all users.");
+        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.");
@@ -3186,7 +3224,7 @@
         pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
         pw.println("       [--install-reason 0/1/2/3/4] [--originating-uri URI]");
         pw.println("       [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
-        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
         pw.println("       [--enable-rollback]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
         pw.println("       [--apex] [--wait TIMEOUT]");
@@ -3209,7 +3247,7 @@
         pw.println("      --referrer: set URI that instigated the install of the app");
         pw.println("      --pkg: specify expected package name of app being installed");
         pw.println("      --abi: override the default ABI of the platform");
-        pw.println("      --instantapp: cause the app to be installed as an ephemeral install app");
+        pw.println("      --instant: cause the app to be installed as an ephemeral install app");
         pw.println("      --full: cause the app to be installed as a non-ephemeral full app");
         pw.println("      --install-location: force the install location:");
         pw.println("          0=auto, 1=internal only, 2=prefer external");
@@ -3222,11 +3260,20 @@
         pw.println("          for pre-reboot verification to complete. If TIMEOUT is not");
         pw.println("          specified it will wait for " + DEFAULT_WAIT_MS + " milliseconds.");
         pw.println("");
+        pw.println("  install-existing [--user USER_ID|all|current]");
+        pw.println("       [--instant] [--full] [--wait] [--restrict-permissions] PACKAGE");
+        pw.println("    Installs an existing application for a new user.  Options are:");
+        pw.println("      --user: install for the given user.");
+        pw.println("      --instant: install as an instant app");
+        pw.println("      --full: install as a full app");
+        pw.println("      --wait: wait until the package is installed");
+        pw.println("      --restrict-permissions: don't whitelist restricted permissions");
+        pw.println("");
         pw.println("  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
         pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
         pw.println("       [--install-reason 0/1/2/3/4] [--originating-uri URI]");
         pw.println("       [--referrer URI] [--abi ABI_NAME] [--force-sdk]");
-        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--preload] [--instant] [--full] [--dont-kill]");
         pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]");
         pw.println("       [--multi-package] [--staged]");
         pw.println("    Like \"install\", but starts an install session.  Use \"install-write\"");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 5972468..4349ea7 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;
 
@@ -4013,13 +4015,18 @@
     }
 
     void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
-            int userHandle, String[] disallowedPackages) {
+            @UserIdInt int userHandle, @Nullable Set<String> installablePackages,
+            String[] disallowedPackages) {
+        final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
+                Trace.TRACE_TAG_PACKAGE_MANAGER);
+        t.traceBegin("createNewUser-" + userHandle);
         String[] volumeUuids;
         String[] names;
         int[] appIds;
         String[] seinfos;
         int[] targetSdkVersions;
         int packagesCount;
+        final boolean skipPackageWhitelist = installablePackages == null;
         synchronized (mLock) {
             Collection<PackageSetting> packages = mPackages.values();
             packagesCount = packages.size();
@@ -4035,6 +4042,7 @@
                     continue;
                 }
                 final boolean shouldInstall = ps.isSystem() &&
+                        (skipPackageWhitelist || installablePackages.contains(ps.name)) &&
                         !ArrayUtils.contains(disallowedPackages, ps.name) &&
                         !ps.pkg.applicationInfo.hiddenUntilInstalled;
                 // Only system apps are initially installed.
@@ -4051,6 +4059,7 @@
                 targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
             }
         }
+        t.traceBegin("createAppData");
         for (int i = 0; i < packagesCount; i++) {
             if (names[i] == null) {
                 continue;
@@ -4064,9 +4073,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 3f2cadeb..6b4ef69 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -301,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");
-                mPreRebootVerificationHandler.startPreRebootVerification(session);
+                mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
                 return;
             }
             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
@@ -318,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());
 
@@ -410,71 +410,23 @@
         return apkSession;
     }
 
-    private void commitApkSession(@NonNull PackageInstallerSession apkSession,
-            PackageInstallerSession originalSession, boolean preReboot)
-            throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
-        if (preReboot) {
-            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 install APK staged session "
-                                    + originalSession.sessionId + " [" + errorMessage + "]");
-                            originalSession.setStagedSessionFailed(errorCode, errorMessage);
-                            return;
-                        }
-                        mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
-                                originalSession);
-                    });
-            apkSession.commit(receiver.getIntentSender(), false);
-            return;
-        }
-
-        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(originalSession.sessionId, apkSession.sessionId);
-            } catch (RemoteException re) {
-                // Cannot happen, the rollback manager is in the same process.
-            }
-        }
-
-        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
-        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 "
-                    + originalSession.sessionId + " [" + 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, 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())
@@ -486,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) {
@@ -518,14 +470,80 @@
                             "Failed to add a child session " + apkChildSession.sessionId);
                 }
             }
-            commitApkSession(apkParentSession, session, 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);
-        mPreRebootVerificationHandler.startPreRebootVerification(session);
+        mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
     @Nullable
@@ -652,7 +670,7 @@
         if (!session.isStagedSessionReady()) {
             // The framework got restarted before the pre-reboot verification could complete,
             // restart the verification.
-            mPreRebootVerificationHandler.startPreRebootVerification(session);
+            mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
@@ -736,7 +754,16 @@
 
         @Override
         public void handleMessage(Message msg) {
-            PackageInstallerSession session = (PackageInstallerSession) msg.obj;
+            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);
@@ -754,20 +781,20 @@
         }
 
         // Method for starting the pre-reboot verification
-        private void startPreRebootVerification(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, session).sendToTarget();
+        private void startPreRebootVerification(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Start_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, session).sendToTarget();
+        private void notifyPreRebootVerification_Start_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Apex_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, session).sendToTarget();
+        private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
         }
 
-        private void notifyPreRebootVerification_Apk_Complete(PackageInstallerSession session) {
-            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, session).sendToTarget();
+        private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
         }
 
         /**
@@ -777,7 +804,7 @@
          */
         private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
             Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
-            notifyPreRebootVerification_Start_Complete(session);
+            notifyPreRebootVerification_Start_Complete(session.sessionId);
         }
 
         /**
@@ -807,7 +834,7 @@
                 }
             }
 
-            notifyPreRebootVerification_Apex_Complete(session);
+            notifyPreRebootVerification_Apex_Complete(session.sessionId);
         }
 
         /**
@@ -818,7 +845,7 @@
          */
         private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
             if (!sessionContainsApk(session)) {
-                notifyPreRebootVerification_Apk_Complete(session);
+                notifyPreRebootVerification_Apk_Complete(session.sessionId);
                 return;
             }
 
@@ -826,8 +853,8 @@
                 Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
                         + session.sessionId + " by performing a dry-run install");
 
-                // installApksInSession will notify the handler when APK verification is complete
-                installApksInSession(session, /* preReboot */ true);
+                // 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());
@@ -856,7 +883,8 @@
                                 + session.sessionId);
                     }
                 } catch (RemoteException re) {
-                    // Cannot happen, the rollback manager is in the same process.
+                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                            + session.sessionId, re);
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 81723cb..5f86708 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -250,6 +250,9 @@
 
     private static final IBinder mUserRestriconToken = new Binder();
 
+    /** Installs system packages based on user-type. */
+    private final UserSystemPackageInstaller mSystemPackageInstaller;
+
     /**
      * Internal non-parcelable wrapper for UserInfo that is not exposed to other system apps.
      */
@@ -550,6 +553,7 @@
             readUserListLP();
             sInstance = this;
         }
+        mSystemPackageInstaller = new UserSystemPackageInstaller(this);
         mLocalService = new LocalService();
         LocalServices.addService(UserManagerInternal.class, mLocalService);
         mLockPatternUtils = new LockPatternUtils(mContext);
@@ -2253,10 +2257,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);
@@ -2712,14 +2717,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;
@@ -2770,17 +2786,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;
@@ -2829,11 +2835,23 @@
                     }
                 }
             }
+
+            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);
-            mPm.createNewUser(userId, disallowedPackages);
+            t.traceEnd();
+
+            final Set<String> installablePackages =
+                    mSystemPackageInstaller.getInstallablePackagesForUserType(flags);
+            t.traceBegin("PM.createNewUser");
+            mPm.createNewUser(userId, installablePackages, disallowedPackages);
+            t.traceEnd();
+
             userInfo.partial = false;
             synchronized (mPackagesLock) {
                 writeUserLP(userData);
@@ -2848,7 +2866,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,
@@ -2861,6 +2883,11 @@
         return userInfo;
     }
 
+    /** Install/uninstall system packages for all users based on their user-type, as applicable. */
+    boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+        return mSystemPackageInstaller.installWhitelistedSystemPackages(isFirstBoot, isUpgrade);
+    }
+
     @VisibleForTesting
     UserData putUserInfo(UserInfo userInfo) {
         final UserData userData = new UserData();
@@ -3847,6 +3874,10 @@
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
         pw.println("  Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
         pw.println("  User version: " + mUserVersion);
+
+        // Dump package whitelist
+        pw.println();
+        mSystemPackageInstaller.dump(pw);
     }
 
     private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) {
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
new file mode 100644
index 0000000..036d1e8
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Responsible for un/installing system packages based on user type.
+ *
+ * <p>Uses the SystemConfig's install-in-user-type whitelist;
+ * see {@link SystemConfig#getAndClearPackageToUserTypeWhitelist} and
+ * {@link SystemConfig#getAndClearPackageToUserTypeBlacklist}.
+ *
+ * <p>If {@link #isEnforceMode()} is false, then all system packages are always installed for all
+ * users. The following applies when it is true.
+ *
+ * Any package can be in one of three states in the SystemConfig whitelist
+ * <ol>
+ *     <li>Explicitly blacklisted for a particular user type</li>
+ *     <li>Explicitly whitelisted for a particular user type</li>
+ *     <li>Not mentioned at all, for any user type (neither whitelisted nor blacklisted)</li>
+ * </ol>
+ * Blacklisting always takes precedence - if a package is blacklisted for a particular user,
+ * it won't be installed on that type of user (even if it is also whitelisted for that user).
+ * Next comes whitelisting - if it is whitelisted for a particular user, it will be installed on
+ * that type of user (as long as it isn't blacklisted).
+ * Finally, if the package is not mentioned at all (i.e. neither whitelisted nor blacklisted for
+ * any user types) in the SystemConfig 'install-in-user-type' lists
+ * then:
+ * <ul>
+ *     <li>If {@link #isImplicitWhitelistMode()}, the package is implicitly treated as whitelisted
+ *          for all users</li>
+ *     <li>Otherwise, the package is implicitly treated as blacklisted for all non-SYSTEM users</li>
+ *     <li>Either way, for {@link UserHandle#USER_SYSTEM}, the package will be implicitly
+ *          whitelisted so that it can be used for local development purposes.</li>
+ * </ul>
+ */
+class UserSystemPackageInstaller {
+    private static final String TAG = "UserManagerService";
+
+    /**
+     * System Property whether to only install system packages on a user if they're whitelisted for
+     * that user type. These are flags and can be freely combined.
+     * <ul>
+     * <li> 0 (0b000) - disable whitelist (install all system packages; no logging)</li>
+     * <li> 1 (0b001) - enforce (only install system packages if they are whitelisted)</li>
+     * <li> 2 (0b010) - log (log when a non-whitelisted package is run)</li>
+     * <li> 4 (0b100) - implicitly whitelist any package not mentioned in the whitelist</li>
+     * <li>-1         - use device default (as defined in res/res/values/config.xml)</li>
+     * </ul>
+     * Note: This list must be kept current with config_userTypePackageWhitelistMode in
+     * frameworks/base/core/res/res/values/config.xml
+     */
+    static final String PACKAGE_WHITELIST_MODE_PROP = "persist.debug.user.package_whitelist_mode";
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE = 0;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE = 0b001;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_LOG = 0b010;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST = 0b100;
+    static final int USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT = -1;
+
+    @IntDef(flag = true, prefix = "USER_TYPE_PACKAGE_WHITELIST_MODE_", value = {
+            USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_LOG,
+            USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PackageWhitelistMode {}
+
+    /**
+     * Maps system package manifest names to the user flags on which they should be initially
+     * installed.
+     * <p>Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+     * any user, are purposefully still present in this list.
+     */
+    private final ArrayMap<String, Integer> mWhitelitsedPackagesForUserTypes;
+
+    private final UserManagerService mUm;
+
+    UserSystemPackageInstaller(UserManagerService ums) {
+        mUm = ums;
+        mWhitelitsedPackagesForUserTypes =
+                determineWhitelistedPackagesForUserTypes(SystemConfig.getInstance());
+    }
+
+    /** Constructor for testing purposes. */
+    @VisibleForTesting
+    UserSystemPackageInstaller(UserManagerService ums, ArrayMap<String, Integer> whitelist) {
+        mUm = ums;
+        mWhitelitsedPackagesForUserTypes = whitelist;
+    }
+
+    /**
+     * During OTAs and first boot, install/uninstall all system packages for all users based on the
+     * user's UserInfo flags and the SystemConfig whitelist.
+     * We do NOT uninstall packages during an OTA though.
+     *
+     * This is responsible for enforcing the whitelist for pre-existing users (i.e. USER_SYSTEM);
+     * enforcement for new users is done when they are created in UserManagerService.createUser().
+     */
+    boolean installWhitelistedSystemPackages(boolean isFirstBoot, boolean isUpgrade) {
+        final int mode = getWhitelistMode();
+        checkWhitelistedSystemPackages(mode);
+        if (!isUpgrade && !isFirstBoot) {
+            return false;
+        }
+        Slog.i(TAG, "Reviewing whitelisted packages due to "
+                + (isFirstBoot ? "[firstBoot]" : "") + (isUpgrade ? "[upgrade]" : ""));
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        // Install/uninstall system packages per user.
+        for (int userId : mUm.getUserIds()) {
+            final Set<String> userWhitelist = getInstallablePackagesForUserId(userId);
+            pmInt.forEachPackage(pkg -> {
+                if (!pkg.isSystem()) {
+                    return;
+                }
+                final boolean install =
+                        (userWhitelist == null || userWhitelist.contains(pkg.packageName))
+                        && !pkg.applicationInfo.hiddenUntilInstalled;
+                if (isUpgrade && !isFirstBoot && !install) {
+                    return; // To be careful, we don’t uninstall apps during OTAs
+                }
+                final boolean changed = pmInt.setInstalled(pkg, userId, install);
+                if (changed) {
+                    Slog.i(TAG, (install ? "Installed " : "Uninstalled ")
+                            + pkg.packageName + " for user " + userId);
+                }
+            });
+        }
+        return true;
+    }
+
+    /**
+     * Checks whether the system packages and the mWhitelistedPackagesForUserTypes whitelist are
+     * in 1-to-1 correspondence.
+     */
+    private void checkWhitelistedSystemPackages(@PackageWhitelistMode int mode) {
+        if (!isLogMode(mode) && !isEnforceMode(mode)) {
+            return;
+        }
+        Slog.v(TAG,  "Checking that all system packages are whitelisted.");
+        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+        // Check whether all whitelisted packages are indeed on the system.
+        for (String pkgName : allWhitelistedPackages) {
+            PackageParser.Package pkg = pmInt.getPackage(pkgName);
+            if (pkg == null) {
+                Slog.w(TAG, pkgName + " is whitelisted but not present.");
+            } else if (!pkg.isSystem()) {
+                Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
+            }
+        }
+
+        // Check whether all system packages are indeed whitelisted.
+        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+            return;
+        }
+        final boolean doWtf = isEnforceMode(mode);
+        pmInt.forEachPackage(pkg -> {
+            if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.manifestPackageName)) {
+                final String msg = "System package " + pkg.manifestPackageName
+                        + " is not whitelisted using 'install-in-user-type' in SystemConfig "
+                        + "for any user types!";
+                if (doWtf) {
+                    Slog.wtf(TAG, msg);
+                } else {
+                    Slog.e(TAG, msg);
+                }
+            }
+        });
+    }
+
+    /** Whether to only install system packages in new users for which they are whitelisted. */
+    boolean isEnforceMode() {
+        return isEnforceMode(getWhitelistMode());
+    }
+
+    /**
+     * Whether to log a warning concerning potential problems with the user-type package whitelist.
+     */
+    boolean isLogMode() {
+        return isLogMode(getWhitelistMode());
+    }
+
+    /**
+     * Whether to treat all packages that are not mentioned at all in the whitelist to be implicitly
+     * whitelisted for all users.
+     */
+    boolean isImplicitWhitelistMode() {
+        return isImplicitWhitelistMode(getWhitelistMode());
+    }
+
+    /** See {@link #isEnforceMode()}. */
+    private static boolean isEnforceMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0;
+    }
+
+    /** See {@link #isLogMode()}. */
+    private static boolean isLogMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_LOG) != 0;
+    }
+
+    /** See {@link #isImplicitWhitelistMode()}. */
+    private static boolean isImplicitWhitelistMode(int whitelistMode) {
+        return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST) != 0;
+    }
+
+    /** Gets the PackageWhitelistMode for use of {@link #mWhitelitsedPackagesForUserTypes}. */
+    private @PackageWhitelistMode int getWhitelistMode() {
+        final int runtimeMode = SystemProperties.getInt(
+                PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+        if (runtimeMode != USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT) {
+            return runtimeMode;
+        }
+        return Resources.getSystem()
+                .getInteger(com.android.internal.R.integer.config_userTypePackageWhitelistMode);
+    }
+
+    /**
+     * Gets the system packages names that should be installed on the given user.
+     * See {@link #getInstallablePackagesForUserType(int)}.
+     */
+    private @Nullable Set<String> getInstallablePackagesForUserId(@UserIdInt int userId) {
+        return getInstallablePackagesForUserType(mUm.getUserInfo(userId).flags);
+    }
+
+    /**
+     * Gets the system package names that should be installed on a user with the given flags, as
+     * determined by SystemConfig, the whitelist mode, and the apps actually on the device.
+     * Names are the {@link PackageParser.Package#packageName}, not necessarily the manifest names.
+     *
+     * Returns null if all system packages should be installed (due enforce-mode being off).
+     */
+    @Nullable Set<String> getInstallablePackagesForUserType(int flags) {
+        final int mode = getWhitelistMode();
+        if (!isEnforceMode(mode)) {
+            return null;
+        }
+        final boolean isSystemUser = (flags & UserInfo.FLAG_SYSTEM) != 0;
+        final boolean isImplicitWhitelistMode = isImplicitWhitelistMode(mode);
+        final Set<String> whitelistedPackages = getWhitelistedPackagesForUserType(flags);
+
+        final Set<String> installPackages = new ArraySet<>();
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+        pmInt.forEachPackage(pkg -> {
+            if (!pkg.isSystem()) {
+                return;
+            }
+            if (shouldInstallPackage(pkg, mWhitelitsedPackagesForUserTypes,
+                    whitelistedPackages, isImplicitWhitelistMode, isSystemUser)) {
+                // Although the whitelist uses manifest names, this function returns packageNames.
+                installPackages.add(pkg.packageName);
+            }
+        });
+        return installPackages;
+    }
+
+    /**
+     * Returns whether the given system package should be installed on the given user, based on the
+     * the given whitelist of system packages.
+     *
+     * @param sysPkg the system package. Must be a system package; no verification for this is done.
+     * @param userTypeWhitelist map of package manifest names to user flags on which they should be
+     *                          installed
+     * @param userWhitelist set of package manifest names that should be installed on this
+     *                      particular user. This must be consistent with userTypeWhitelist, but is
+     *                      passed in separately to avoid repeatedly calculating it from
+     *                      userTypeWhitelist.
+     * @param isImplicitWhitelistMode whether non-mentioned packages are implicitly whitelisted.
+     * @param isSystemUser whether the user is USER_SYSTEM (which gets special treatment).
+     */
+    @VisibleForTesting
+    static boolean shouldInstallPackage(PackageParser.Package sysPkg,
+            @NonNull ArrayMap<String, Integer> userTypeWhitelist,
+            @NonNull Set<String> userWhitelist, boolean isImplicitWhitelistMode,
+            boolean isSystemUser) {
+
+        final String pkgName = sysPkg.manifestPackageName;
+        boolean install = (isImplicitWhitelistMode && !userTypeWhitelist.containsKey(pkgName))
+                || userWhitelist.contains(pkgName);
+
+        // For the purposes of local development, any package that isn't even mentioned in the
+        // whitelist at all is implicitly treated as whitelisted for the SYSTEM user.
+        if (!install && isSystemUser && !userTypeWhitelist.containsKey(pkgName)) {
+            install = true;
+            Slog.e(TAG, "System package " + pkgName + " is not mentioned "
+                    + "in SystemConfig's 'install-in-user-type' but we are "
+                    + "implicitly treating it as whitelisted for the SYSTEM user.");
+        }
+        return install;
+    }
+
+    /**
+     * Gets the package manifest names that are whitelisted for a user with the given flags,
+     * as determined by SystemConfig.
+     */
+    @VisibleForTesting
+    @NonNull Set<String> getWhitelistedPackagesForUserType(int flags) {
+        Set<String> installablePkgs = new ArraySet<>(mWhitelitsedPackagesForUserTypes.size());
+        for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+            String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+            int whitelistedUserTypes = mWhitelitsedPackagesForUserTypes.valueAt(i);
+            if ((flags & whitelistedUserTypes) != 0) {
+                installablePkgs.add(pkgName);
+            }
+        }
+        return installablePkgs;
+    }
+
+    /**
+     * Set of package manifest names that are included anywhere in the package-to-user-type
+     * whitelist, as determined by SystemConfig.
+     *
+     * Packages that are whitelisted, but then blacklisted so that they aren't to be installed on
+     * any user, are still present in this list, since that is a valid scenario (e.g. if an OEM
+     * completely blacklists an AOSP app).
+     */
+    private Set<String> getWhitelistedSystemPackages() {
+        return mWhitelitsedPackagesForUserTypes.keySet();
+    }
+
+    /**
+     * Returns a map of package manifest names to the user flags on which it is to be installed.
+     * Also, clears this data from SystemConfig where it was stored inefficiently (and therefore
+     * should be called exactly once, even if the data isn't useful).
+     *
+     * Any system packages not present in this map should not even be on the device at all.
+     * To enforce this:
+     * <ul>
+     *  <li>Illegal user types are ignored.</li>
+     *  <li>Packages that never whitelisted at all (even if they are explicitly blacklisted) are
+     *          ignored.</li>
+     *  <li>Packages that are blacklisted whenever they are whitelisted will be stored with the
+     *          flag 0 (since this is a valid scenario, e.g. if an OEM completely blacklists an AOSP
+     *          app).</li>
+     * </ul>
+     */
+    @VisibleForTesting
+    static ArrayMap<String, Integer> determineWhitelistedPackagesForUserTypes(
+            SystemConfig sysConfig) {
+
+        final ArrayMap<String, Set<String>> whitelist =
+                sysConfig.getAndClearPackageToUserTypeWhitelist();
+        // result maps packageName -> userTypes on which the package should be installed.
+        final ArrayMap<String, Integer> result = new ArrayMap<>(whitelist.size() + 1);
+        // First, do the whitelisted user types.
+        for (int i = 0; i < whitelist.size(); i++) {
+            final String pkgName = whitelist.keyAt(i);
+            final int flags = getFlagsFromUserTypes(whitelist.valueAt(i));
+            if (flags != 0) {
+                result.put(pkgName, flags);
+            }
+        }
+        // Then, un-whitelist any blacklisted user types.
+        // TODO(b/141370854): Right now, the blacklist is actually just an 'unwhitelist'. Which
+        //                    direction we go depends on how we design user subtypes, which is still
+        //                    being designed. For now, unwhitelisting works for current use-cases.
+        final ArrayMap<String, Set<String>> blacklist =
+                sysConfig.getAndClearPackageToUserTypeBlacklist();
+        for (int i = 0; i < blacklist.size(); i++) {
+            final String pkgName = blacklist.keyAt(i);
+            final int nonFlags = getFlagsFromUserTypes(blacklist.valueAt(i));
+            final Integer flags = result.get(pkgName);
+            if (flags != null) {
+                result.put(pkgName, flags & ~nonFlags);
+            }
+        }
+        // Regardless of the whitelists/blacklists, ensure mandatory packages.
+        result.put("android",
+                UserInfo.FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+        return result;
+    }
+
+    /** Converts a user types, as used in SystemConfig, to a UserInfo flag. */
+    private static int getFlagsFromUserTypes(Iterable<String> userTypes) {
+        int flags = 0;
+        for (String type : userTypes) {
+            switch (type) {
+                case "GUEST":
+                    flags |= UserInfo.FLAG_GUEST;
+                    break;
+                case "RESTRICTED":
+                    flags |= UserInfo.FLAG_RESTRICTED;
+                    break;
+                case "MANAGED_PROFILE":
+                    flags |= UserInfo.FLAG_MANAGED_PROFILE;
+                    break;
+                case "EPHEMERAL":
+                    flags |= UserInfo.FLAG_EPHEMERAL;
+                    break;
+                case "DEMO":
+                    flags |= UserInfo.FLAG_DEMO;
+                    break;
+                case "FULL":
+                    flags |= UserInfo.FLAG_FULL;
+                    break;
+                case "SYSTEM":
+                    flags |= UserInfo.FLAG_SYSTEM;
+                    break;
+                case "PROFILE":
+                    flags |= UserInfo.PROFILE_FLAGS_MASK;
+                    break;
+                default:
+                    Slog.w(TAG, "SystemConfig contained an invalid user type: " + type);
+                    break;
+                // Other UserInfo flags are forbidden.
+                // In particular, FLAG_INITIALIZED, FLAG_DISABLED, FLAG_QUIET_MODE are inapplicable.
+                // The following are invalid now, but are reconsiderable: FLAG_PRIMARY, FLAG_ADMIN.
+            }
+        }
+        return flags;
+    }
+
+    void dump(PrintWriter pw) {
+        for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+            final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
+            final String whitelistedUserTypes =
+                    UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
+            pw.println("    " + pkgName + ": " + whitelistedUserTypes);
+        }
+    }
+}
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 a9e3f04..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;
@@ -793,62 +793,68 @@
 
         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) {
+            @NonNull PermissionsState permissionsState, @NonNull String permissionName,
+            boolean useRequestedPermissionsForLegacyApps) {
         boolean hasPermission = permissionsState.hasPermission(permissionName,
                 UserHandle.getUserId(uid));
 
-        if (!hasPermission && mSettings.isPermissionRuntime(permissionName)) {
+        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++) {
@@ -891,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;
     }
 
     /**
@@ -906,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) {
@@ -933,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(
@@ -1495,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
@@ -1605,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(
@@ -2517,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
@@ -2537,7 +2556,7 @@
                                                 bp.getSourcePackageName())) {
                                             if (!bp.isRemoved()) {
                                                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED
-                                                        | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                                        | FLAG_PERMISSION_REVOKED_COMPAT;
                                                 wasChanged = true;
                                             }
                                         }
@@ -2652,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 ||
@@ -4446,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/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..20bab55
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -0,0 +1,454 @@
+/*
+ * 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);
+    }
+
+    /** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
+    public static boolean isEnabled(IProtoLogGroup group) {
+        return group.isLogToProto()
+                || (group.isLogToProto() && getSingleInstance().isProtoEnabled());
+    }
+
+    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());
+
+            if (args != null) {
+                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 6769fe0..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,8 +66,18 @@
     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;
 
     /**
@@ -74,23 +92,20 @@
      * The timestamp is not applicable for all rollback states, but we make
      * sure to keep it non-null to avoid potential errors there.
      */
+    @GuardedBy("mLock")
     private @NonNull Instant mTimestamp;
 
     /**
-     * The session ID for the staged session if this rollback data represents a staged session,
-     * {@code -1} otherwise.
-     */
-    private final int mStagedSessionId;
-
-    /**
      * The current state of the rollback.
      * ENABLING, AVAILABLE, or COMMITTED.
      */
+    @GuardedBy("mLock")
     private @RollbackState int mState;
 
     /**
      * The id of the post-reboot apk session for a staged install, if any.
      */
+    @GuardedBy("mLock")
     private int mApkSessionId = -1;
 
     /**
@@ -98,10 +113,17 @@
      * 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.
+    @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.
      *
      * @param rollbackId the id of the rollback.
@@ -135,8 +157,23 @@
     }
 
     /**
+     * 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.
      */
+    @GuardedBy("getLock")
     boolean isStaged() {
         return info.isStaged();
     }
@@ -151,6 +188,7 @@
     /**
      * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
      */
+    @GuardedBy("getLock")
     Instant getTimestamp() {
         return mTimestamp;
     }
@@ -158,6 +196,7 @@
     /**
      * Sets the time at which upgrade occurred.
      */
+    @GuardedBy("getLock")
     void setTimestamp(Instant timestamp) {
         mTimestamp = timestamp;
     }
@@ -173,6 +212,7 @@
     /**
      * Returns true if the rollback is in the ENABLING state.
      */
+    @GuardedBy("getLock")
     boolean isEnabling() {
         return mState == ROLLBACK_STATE_ENABLING;
     }
@@ -180,6 +220,7 @@
     /**
      * Returns true if the rollback is in the AVAILABLE state.
      */
+    @GuardedBy("getLock")
     boolean isAvailable() {
         return mState == ROLLBACK_STATE_AVAILABLE;
     }
@@ -187,6 +228,7 @@
     /**
      * Returns true if the rollback is in the COMMITTED state.
      */
+    @GuardedBy("getLock")
     boolean isCommitted() {
         return mState == ROLLBACK_STATE_COMMITTED;
     }
@@ -194,6 +236,7 @@
     /**
      * Sets the state of the rollback to AVAILABLE.
      */
+    @GuardedBy("getLock")
     void setAvailable() {
         mState = ROLLBACK_STATE_AVAILABLE;
     }
@@ -201,6 +244,7 @@
     /**
      * Sets the state of the rollback to COMMITTED.
      */
+    @GuardedBy("getLock")
     void setCommitted() {
         mState = ROLLBACK_STATE_COMMITTED;
     }
@@ -208,6 +252,7 @@
     /**
      * Returns the id of the post-reboot apk session for a staged install, if any.
      */
+    @GuardedBy("getLock")
     int getApkSessionId() {
         return mApkSessionId;
     }
@@ -215,6 +260,7 @@
     /**
      * Sets the id of the post-reboot apk session for a staged install.
      */
+    @GuardedBy("getLock")
     void setApkSessionId(int apkSessionId) {
         mApkSessionId = apkSessionId;
     }
@@ -223,6 +269,7 @@
      * 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;
     }
@@ -231,10 +278,65 @@
      * 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";
@@ -254,6 +356,7 @@
         throw new ParseException("Invalid rollback state: " + state, 0);
     }
 
+    @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 96d284b..e8e448a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -282,8 +282,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.isAvailable()) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isAvailable()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -298,8 +300,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.isCommitted()) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isCommitted()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -332,8 +336,11 @@
                     Iterator<Rollback> iter = mRollbacks.iterator();
                     while (iter.hasNext()) {
                         Rollback rollback = iter.next();
-                        rollback.setTimestamp(rollback.getTimestamp().plusMillis(timeDifference));
-                        saveRollback(rollback);
+                        synchronized (rollback.getLock()) {
+                            rollback.setTimestamp(
+                                    rollback.getTimestamp().plusMillis(timeDifference));
+                            saveRollback(rollback);
+                        }
                     }
                 }
             }
@@ -358,86 +365,94 @@
         Slog.i(TAG, "Initiating rollback");
 
         Rollback rollback = getRollbackForId(rollbackId);
-        if (rollback == null || !rollback.isAvailable()) {
+        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);
@@ -450,21 +465,22 @@
                                 // 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?
+                                // 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.
@@ -473,32 +489,31 @@
 
                                 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.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;
         }
     }
 
@@ -534,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;
                     }
                 }
             }
@@ -578,12 +591,16 @@
                 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();
         });
 
@@ -617,17 +634,15 @@
             Set<String> apexPackageNames = new HashSet<>();
             synchronized (mLock) {
                 for (Rollback rollback : mRollbacks) {
-                    if (rollback.isStaged()) {
-                        if (rollback.isEnabling()) {
-                            enabling.add(rollback);
-                        } else if (rollback.isRestoreUserDataInProgress()) {
-                            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());
                         }
                     }
                 }
@@ -635,30 +650,32 @@
 
             for (Rollback rollback : enabling) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                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);
+                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.getStagedSessionId());
-                // TODO: What if session is null?
-                if (session != null) {
-                    if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
-                        synchronized (mLock) {
+                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);
                     }
                 }
             }
@@ -687,23 +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.isEnabling() || rollback.isAvailable()) {
-                    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);
                     }
                 }
             }
@@ -760,16 +773,18 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                if (!rollback.isAvailable()) {
-                    continue;
-                }
-                if (!now.isBefore(
+                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();
+                        iter.remove();
+                        deleteRollback(rollback);
+                    } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
+                        oldest = rollback.getTimestamp();
+                    }
                 }
             }
         }
@@ -877,10 +892,12 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                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;
+                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;
+                    }
                 }
             }
         }
@@ -979,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);
@@ -992,7 +1010,7 @@
             return false;
         }
 
-        synchronized (mLock) {
+        synchronized (rollback.getLock()) {
             rollback.info.getPackages().add(packageRollbackInfo);
         }
         return true;
@@ -1020,27 +1038,31 @@
             // staged installs
             for (int i = 0; i < mRollbacks.size(); i++) {
                 Rollback rollback = mRollbacks.get(i);
-                if (!rollback.isEnabling()) {
-                    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);
+                    }
                 }
             }
         }
@@ -1053,11 +1075,13 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback candidate = mRollbacks.get(i);
-                if (candidate.isRestoreUserDataInProgress()) {
-                    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;
+                        }
                     }
                 }
             }
@@ -1068,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);
+                }
             }
         }
     }
@@ -1147,7 +1173,6 @@
                 for (int i = 0; i < mRollbacks.size(); ++i) {
                     Rollback candidate = mRollbacks.get(i);
                     if (candidate.getStagedSessionId() == originalSessionId) {
-                        candidate.setApkSessionId(apkSessionId);
                         rollback = candidate;
                         break;
                     }
@@ -1162,7 +1187,10 @@
             }
 
             if (rollback != null) {
-                saveRollback(rollback);
+                synchronized (rollback.getLock()) {
+                    rollback.setApkSessionId(apkSessionId);
+                    saveRollback(rollback);
+                }
             }
         });
     }
@@ -1207,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();
     }
 
     /**
@@ -1273,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
@@ -1328,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.setAvailable();
-            rollback.setTimestamp(Instant.now());
-        }
+        rollback.setAvailable();
+        rollback.setTimestamp(Instant.now());
         saveRollback(rollback);
 
         // TODO(zezeozue): Provide API to explicitly start observing instead
@@ -1343,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);
     }
@@ -1372,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()) {
@@ -1398,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();
@@ -1416,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);
@@ -1430,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.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();
-                if (rollback.isCommitted()) {
-                    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();
             }
         }
     }
@@ -1516,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 772c53f..b6d1f18 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -27,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;
@@ -250,6 +252,7 @@
     /**
      * Saves the given rollback to persistent storage.
      */
+    @GuardedBy("rollback.getLock")
     void saveRollback(Rollback rollback) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
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..2b25b89
--- /dev/null
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -0,0 +1,91 @@
+/*
+ * 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.annotation.Nullable;
+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.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ProcfsMemoryUtil {
+    private static final String TAG = "ProcfsMemoryUtil";
+
+    private static final Pattern STATUS_MEMORY_STATS =
+            Pattern.compile(String.join(
+                    ".*",
+                    "Uid:\\s*(\\d+)\\s*",
+                    "VmHWM:\\s*(\\d+)\\s*kB",
+                    "VmRSS:\\s*(\\d+)\\s*kB",
+                    "RssAnon:\\s*(\\d+)\\s*kB",
+                    "VmSwap:\\s*(\\d+)\\s*kB"), Pattern.DOTALL);
+
+    private ProcfsMemoryUtil() {}
+
+    /**
+     * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS,
+     * VmSwap fields in /proc/pid/status in kilobytes or null if not available.
+     */
+    @Nullable
+    static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        return parseMemorySnapshotFromStatus(readFile("/proc/" + pid + "/status"));
+    }
+
+    @VisibleForTesting
+    @Nullable
+    static MemorySnapshot parseMemorySnapshotFromStatus(String contents) {
+        if (contents.isEmpty()) {
+            return null;
+        }
+        try {
+            final Matcher matcher = STATUS_MEMORY_STATS.matcher(contents);
+            if (matcher.find()) {
+                final MemorySnapshot snapshot = new MemorySnapshot();
+                snapshot.uid = Integer.parseInt(matcher.group(1));
+                snapshot.rssHighWaterMarkInKilobytes = Integer.parseInt(matcher.group(2));
+                snapshot.rssInKilobytes = Integer.parseInt(matcher.group(3));
+                snapshot.anonRssInKilobytes = Integer.parseInt(matcher.group(4));
+                snapshot.swapInKilobytes = Integer.parseInt(matcher.group(5));
+                return snapshot;
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+        }
+        return null;
+    }
+
+    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 "";
+        }
+    }
+
+    static final class MemorySnapshot {
+        public int uid;
+        public int rssHighWaterMarkInKilobytes;
+        public int rssInKilobytes;
+        public int anonRssInKilobytes;
+        public int swapInKilobytes;
+    }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c76bbb0..67830a9 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -27,9 +27,9 @@
 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 android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -138,9 +138,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;
 
@@ -243,6 +244,13 @@
             "zygote",
             "zygote64",
     };
+    /**
+     * Lowest available uid for apps.
+     *
+     * <p>Used to quickly discard memory snapshots of the zygote forks from native process
+     * measurements.
+     */
+    private static final int MIN_APP_UID = 10_000;
 
     private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
 
@@ -1112,13 +1120,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);
         }
     }
@@ -1196,20 +1203,18 @@
     private void pullNativeProcessMemoryState(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
-        final List<String> processNames = Arrays.asList(MEMORY_INTERESTING_NATIVE_PROCESSES);
         int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
-        for (int i = 0; i < pids.length; i++) {
-            int pid = pids[i];
+        for (int pid : pids) {
+            String processName = readCmdlineFromProcfs(pid);
             MemoryStat memoryStat = readMemoryStatFromProcfs(pid);
             if (memoryStat == null) {
                 continue;
             }
             int uid = getUidForPid(pid);
-            String processName = readCmdlineFromProcfs(pid);
-            // Sometimes we get here processName that is not included in the whitelist. It comes
+            // Sometimes we get here a process that is not included in the whitelist. It comes
             // from forking the zygote for an app. We can ignore that sample because this process
             // is collected by ProcessMemoryState.
-            if (!processNames.contains(processName)) {
+            if (isAppUid(uid)) {
                 continue;
             }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
@@ -1237,33 +1242,95 @@
                 LocalServices.getService(
                         ActivityManagerInternal.class).getMemoryStateForProcesses();
         for (ProcessMemoryState managedProcess : managedProcessList) {
-            final long rssHighWaterMarkInBytes =
-                    readRssHighWaterMarkFromProcfs(managedProcess.pid);
-            if (rssHighWaterMarkInBytes == 0) {
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot == null) {
                 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) snapshot.rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(snapshot.rssHighWaterMarkInKilobytes);
             pulledData.add(e);
         }
         int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
-        for (int i = 0; i < pids.length; i++) {
-            final int pid = pids[i];
-            final int uid = getUidForPid(pid);
+        for (int pid : pids) {
             final String processName = readCmdlineFromProcfs(pid);
-            final long rssHighWaterMarkInBytes = readRssHighWaterMarkFromProcfs(pid);
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot == null) {
+                continue;
+            }
+            // Sometimes we get here a process that is not included in the whitelist. It comes
+            // from forking the zygote for an app. We can ignore that sample because this process
+            // is collected by ProcessMemoryState.
+            if (isAppUid(snapshot.uid)) {
+                continue;
+            }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
-            e.writeInt(uid);
+            e.writeInt(snapshot.uid);
             e.writeString(processName);
-            e.writeLong(rssHighWaterMarkInBytes);
+            // RSS high-water mark in bytes.
+            e.writeLong((long) snapshot.rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(snapshot.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) {
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot == null) {
+                continue;
+            }
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(managedProcess.uid);
+            e.writeString(managedProcess.processName);
+            e.writeInt(managedProcess.pid);
+            e.writeInt(managedProcess.oomScore);
+            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) {
+            final String processName = readCmdlineFromProcfs(pid);
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot == null) {
+                continue;
+            }
+            // Sometimes we get here a process that is not included in the whitelist. It comes
+            // from forking the zygote for an app. We can ignore that sample because this process
+            // is collected by ProcessMemoryState.
+            if (isAppUid(snapshot.uid)) {
+                continue;
+            }
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(snapshot.uid);
+            e.writeString(processName);
+            e.writeInt(pid);
+            e.writeInt(-1001);  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+    }
+
+    private static boolean isAppUid(int uid) {
+        return uid >= MIN_APP_UID;
+    }
+
     private void pullSystemIonHeapSize(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2346,6 +2413,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/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/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ad340fe..59f051b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1106,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;
@@ -1127,7 +1123,6 @@
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 int displayId,
                 WindowsForAccessibilityCallback callback) {
-            mContext = windowManagerService.mContext;
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
@@ -1246,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 f3e7384..e488cc9 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -740,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) {
@@ -764,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) {
@@ -784,7 +784,7 @@
                         false /* creating */);
             }
         } finally {
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1002,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 */,
@@ -1026,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) {
@@ -1045,9 +1040,7 @@
 
             kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mService.mWindowManager != null) {
-                mService.mWindowManager.continueSurfaceLayout();
-            }
+            mService.continueWindowLayout();
         }
 
         if (result != null) {
@@ -1096,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 61bf2ce..c54ccd4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1703,7 +1703,7 @@
                 // is not visible if it only contains finishing activities.
                 && mRootActivityContainer.isTopDisplayFocusedStack(stack);
 
-        mAtmService.mWindowManager.deferSurfaceLayout();
+        mAtmService.deferWindowLayout();
         try {
             makeFinishingLocked();
             final TaskRecord task = getTaskRecord();
@@ -1809,7 +1809,7 @@
 
             return FINISH_RESULT_REQUESTED;
         } finally {
-            mAtmService.mWindowManager.continueSurfaceLayout();
+            mAtmService.continueWindowLayout();
         }
     }
 
@@ -2202,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();
+        }
     }
 
     /**
@@ -2547,6 +2549,8 @@
             return;
         }
         mAppWindowToken.setVisibility(visible, mDeferHidingClient);
+        mAtmService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
         mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
     }
 
@@ -2959,6 +2963,11 @@
         if (display != null) {
             display.handleActivitySizeCompatModeIfNeeded(r);
         }
+
+        if (r.mAppWindowToken != null) {
+            r.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController
+                    .notifyAppResumedFinished(r.mAppWindowToken);
+        }
     }
 
     /**
@@ -4309,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 b92625f..2ab3e01 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -757,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
@@ -779,7 +778,7 @@
                     topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
         }
 
-        wm.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (!animate && topActivity != null) {
                 mStackSupervisor.mNoAnimActivities.add(topActivity);
@@ -840,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) {
@@ -1750,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 {
@@ -4360,7 +4360,7 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Update override configurations of all tasks in the stack.
             final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
@@ -4384,7 +4384,7 @@
                         topRunningActivityLocked(), preserveWindows);
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -4516,7 +4516,7 @@
      */
     void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
             @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed,
-            boolean crossUser) {
+            boolean crossUser, ArraySet<Integer> profileIds) {
         boolean focusedStack = mRootActivityContainer.getTopDisplayFocusedStack() == this;
         boolean topTask = true;
         int userId = UserHandle.getUserId(callingUid);
@@ -4527,8 +4527,9 @@
                 continue;
             }
             if (task.effectiveUid != callingUid) {
-                if (task.userId != userId && !crossUser) {
-                    // Skip if the caller does not have cross user permission
+                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()) {
@@ -4940,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 7a3f022..d151f86 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1495,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;
@@ -1561,7 +1561,7 @@
             mRootActivityContainer.resumeFocusedStacksTopActivities();
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1630,7 +1630,7 @@
         }
 
         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;
@@ -1694,7 +1694,7 @@
             }
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1718,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.
@@ -1739,7 +1738,7 @@
             stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds, !PRESERVE_WINDOWS,
                     !DEFER_RESUME);
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -2481,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();
     }
@@ -2731,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(
@@ -2822,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 641b00a..dbf06a5 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1400,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 {
@@ -1436,7 +1436,7 @@
                     startedActivityStack.remove();
                 }
             }
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         postStartActivityProcessing(r, result, startedActivityStack);
@@ -1535,6 +1535,11 @@
             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");
         }
@@ -1545,10 +1550,11 @@
 
         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);
@@ -1654,9 +1660,8 @@
         final boolean isNewClearTask =
                 (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                         == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-        if (mService.getLockTaskController().isInLockTaskMode() && (newTask
-                || mService.getLockTaskController().isLockTaskModeViolation(targetTask,
-                isNewClearTask))) {
+        if (!newTask && mService.getLockTaskController().isLockTaskModeViolation(targetTask,
+                isNewClearTask)) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
@@ -2337,7 +2342,12 @@
                             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
@@ -2538,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.
@@ -2656,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;
@@ -2671,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 d97f0f5..468a13d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -47,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;
@@ -124,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;
@@ -215,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;
@@ -548,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.
@@ -597,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;
@@ -775,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);
-            }
         }
     }
 
@@ -1682,7 +1686,6 @@
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
             ActivityRecord.activityResumedLocked(token);
-            mWindowManager.notifyAppResumedFinished(token);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -2512,6 +2515,12 @@
         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) {
@@ -2519,7 +2528,7 @@
 
             final boolean allowed = isGetTasksAllowed("getTasks", callingPid, callingUid);
             mRootActivityContainer.getRunningTasks(maxNum, list, ignoreActivityType,
-                    ignoreWindowingMode, callingUid, allowed, crossUser);
+                    ignoreWindowingMode, callingUid, allowed, crossUser, callingProfileIds);
         }
 
         return list;
@@ -2899,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);
@@ -4381,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 {
@@ -5092,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,
@@ -5103,9 +5112,7 @@
 
             kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mWindowManager != null) {
-                mWindowManager.continueSurfaceLayout();
-            }
+            continueWindowLayout();
         }
 
         if (result != null) {
@@ -5233,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);
     }
@@ -5318,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;
             }
@@ -6660,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
@@ -6671,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/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index ffd9021..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);
@@ -2503,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();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5ec167e..6462744 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;
@@ -563,6 +564,10 @@
     // Last systemUiVisibility we dispatched to windows.
     private int mLastDispatchedSystemUiVisibility = 0;
 
+    private final ArrayList<TaskStack> mTmpAlwaysOnTopStacks = new ArrayList<>();
+    private final ArrayList<TaskStack> mTmpNormalStacks = new ArrayList<>();
+    private final ArrayList<TaskStack> mTmpHomeStacks = new ArrayList<>();
+
     /** Corner radius that windows should have in order to match the display. */
     private final float mWindowCornerRadius;
 
@@ -582,27 +587,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;
@@ -942,14 +926,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();
 
@@ -1056,9 +1043,10 @@
                 // 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;
             }
         }
@@ -2162,7 +2150,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;
@@ -2379,12 +2366,6 @@
         mDisplayPolicy.switchUser();
     }
 
-    private void resetAnimationBackgroundAnimator() {
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
-        }
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -2916,9 +2897,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);
@@ -3416,14 +3399,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) {
@@ -3877,21 +3852,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
@@ -4310,54 +4270,56 @@
         }
 
         void assignStackOrdering(SurfaceControl.Transaction t) {
-
-            final int HOME_STACK_STATE = 0;
-            final int NORMAL_STACK_STATE = 1;
-            final int ALWAYS_ON_TOP_STATE = 2;
+            if (getParent() == null) {
+                return;
+            }
+            mTmpAlwaysOnTopStacks.clear();
+            mTmpHomeStacks.clear();
+            mTmpNormalStacks.clear();
+            for (int i = 0; i < mChildren.size(); ++i) {
+                final TaskStack s = mChildren.get(i);
+                if (s.isAlwaysOnTop()) {
+                    mTmpAlwaysOnTopStacks.add(s);
+                } else if (s.isActivityTypeHome()) {
+                    mTmpHomeStacks.add(s);
+                } else {
+                    mTmpNormalStacks.add(s);
+                }
+            }
 
             int layer = 0;
-            int layerForAnimationLayer = 0;
-            int layerForBoostedAnimationLayer = 0;
-            int layerForHomeAnimationLayer = 0;
+            // Place home stacks to the bottom.
+            for (int i = 0; i < mTmpHomeStacks.size(); i++) {
+                mTmpHomeStacks.get(i).assignLayer(t, layer++);
+            }
+            // The home animation layer is between the home stacks and the normal stacks.
+            final int layerForHomeAnimationLayer = layer++;
+            int layerForSplitScreenDividerAnchor = layer++;
+            int layerForAnimationLayer = layer++;
+            for (int i = 0; i < mTmpNormalStacks.size(); i++) {
+                final TaskStack s = mTmpNormalStacks.get(i);
+                s.assignLayer(t, layer++);
+                if (s.inSplitScreenWindowingMode()) {
+                    // The split screen divider anchor is located above the split screen window.
+                    layerForSplitScreenDividerAnchor = layer++;
+                }
+                if (s.isTaskAnimating() || s.isAppAnimating()) {
+                    // The animation layer is located above the highest animating stack and no
+                    // higher.
+                    layerForAnimationLayer = layer++;
+                }
+            }
+            // The boosted animation layer is between the normal stacks and the always on top
+            // stacks.
+            final int layerForBoostedAnimationLayer = layer++;
+            for (int i = 0; i < mTmpAlwaysOnTopStacks.size(); i++) {
+                mTmpAlwaysOnTopStacks.get(i).assignLayer(t, layer++);
+            }
 
-            for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
-                for (int i = 0; i < mChildren.size(); i++) {
-                    final TaskStack s = mChildren.get(i);
-                    if (state == HOME_STACK_STATE && !s.isActivityTypeHome()) {
-                        continue;
-                    } else if (state == NORMAL_STACK_STATE && (s.isActivityTypeHome()
-                            || s.isAlwaysOnTop())) {
-                        continue;
-                    } else if (state == ALWAYS_ON_TOP_STATE && !s.isAlwaysOnTop()) {
-                        continue;
-                    }
-                    s.assignLayer(t, layer++);
-                    if (s.inSplitScreenWindowingMode() && mSplitScreenDividerAnchor != null) {
-                        t.setLayer(mSplitScreenDividerAnchor, layer++);
-                    }
-                    if ((s.isTaskAnimating() || s.isAppAnimating())
-                            && state != ALWAYS_ON_TOP_STATE) {
-                        // Ensure the animation layer ends up above the
-                        // highest animating stack and no higher.
-                        layerForAnimationLayer = layer++;
-                    }
-                    if (state != ALWAYS_ON_TOP_STATE) {
-                        layerForBoostedAnimationLayer = layer++;
-                    }
-                }
-                if (state == HOME_STACK_STATE) {
-                    layerForHomeAnimationLayer = layer++;
-                }
-            }
-            if (mAppAnimationLayer != null) {
-                t.setLayer(mAppAnimationLayer, layerForAnimationLayer);
-            }
-            if (mBoostedAppAnimationLayer != null) {
-                t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
-            }
-            if (mHomeAppAnimationLayer != null) {
-                t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
-            }
+            t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
+            t.setLayer(mAppAnimationLayer, layerForAnimationLayer);
+            t.setLayer(mSplitScreenDividerAnchor, layerForSplitScreenDividerAnchor);
+            t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
         }
 
         @Override
@@ -4379,27 +4341,28 @@
 
         @Override
         void onParentChanged() {
-            super.onParentChanged();
             if (getParent() != null) {
-                mAppAnimationLayer = makeChildSurface(null)
-                        .setName("animationLayer")
-                        .build();
-                mBoostedAppAnimationLayer = makeChildSurface(null)
-                        .setName("boostedAnimationLayer")
-                        .build();
-                mHomeAppAnimationLayer = makeChildSurface(null)
-                        .setName("homeAnimationLayer")
-                        .build();
-                mSplitScreenDividerAnchor = makeChildSurface(null)
-                        .setName("splitScreenDividerAnchor")
-                        .build();
-                getPendingTransaction()
-                        .show(mAppAnimationLayer)
-                        .show(mBoostedAppAnimationLayer)
-                        .show(mHomeAppAnimationLayer)
-                        .show(mSplitScreenDividerAnchor);
-                scheduleAnimation();
+                super.onParentChanged(() -> {
+                    mAppAnimationLayer = makeChildSurface(null)
+                            .setName("animationLayer")
+                            .build();
+                    mBoostedAppAnimationLayer = makeChildSurface(null)
+                            .setName("boostedAnimationLayer")
+                            .build();
+                    mHomeAppAnimationLayer = makeChildSurface(null)
+                            .setName("homeAnimationLayer")
+                            .build();
+                    mSplitScreenDividerAnchor = makeChildSurface(null)
+                            .setName("splitScreenDividerAnchor")
+                            .build();
+                    getPendingTransaction()
+                            .show(mAppAnimationLayer)
+                            .show(mBoostedAppAnimationLayer)
+                            .show(mHomeAppAnimationLayer)
+                            .show(mSplitScreenDividerAnchor);
+                });
             } else {
+                super.onParentChanged();
                 mWmService.mTransactionFactory.get()
                         .remove(mAppAnimationLayer)
                         .remove(mBoostedAppAnimationLayer)
@@ -4879,7 +4842,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 b502bd5..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();
 
@@ -2597,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));
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/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 dd9000e..8e0531c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1b7b92b..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 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) {
@@ -150,6 +155,16 @@
                 && !mWin.mGivenInsetsPending);
     }
 
+    /**
+     * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
+     */
+    void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
+        if (fakeTarget == mFakeControlTarget) {
+            return;
+        }
+        mFakeControlTarget = fakeTarget;
+    }
+
     void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
         if (mWin == null) {
             mControlTarget = target;
@@ -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;
@@ -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 bb70495..4ebb553 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -20,6 +20,8 @@
 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.sNewInsetsMode;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,6 +49,10 @@
     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 -> {
@@ -93,7 +99,7 @@
         final int size = controlled.size();
         final InsetsSourceControl[] result = new InsetsSourceControl[size];
         for (int i = 0; i < size; i++) {
-            result[i] = mProviders.get(controlled.get(i)).getControl();
+            result[i] = mProviders.get(controlled.get(i)).getControl(target);
         }
         return result;
     }
@@ -157,7 +163,8 @@
 
     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
-        removeFromControlMaps(previousControlTarget, provider.getSource().getType());
+        removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
+                false /* fake */);
     }
 
     private void onControlChanged(@InternalInsetType int type,
@@ -175,17 +182,47 @@
         }
         provider.updateControlForTarget(target, false /* force */);
         if (previous != null) {
-            removeFromControlMaps(previous, type);
+            removeFromControlMaps(previous, type, false /* fake */);
             mPendingControlChanged.add(previous);
         }
         if (target != null) {
-            addToControlMaps(target, type);
+            addToControlMaps(target, type, false /* fake */);
             mPendingControlChanged.add(target);
         }
     }
 
+    /**
+     * 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) {
+            @InternalInsetType int type, boolean fake) {
         final ArrayList<Integer> array = mControlTargetTypeMap.get(target);
         if (array == null) {
             return;
@@ -194,15 +231,23 @@
         if (array.isEmpty()) {
             mControlTargetTypeMap.remove(target);
         }
-        mTypeControlTargetMap.remove(type);
+        if (fake) {
+            mTypeFakeControlTargetMap.remove(type);
+        } else {
+            mTypeControlTargetMap.remove(type);
+        }
     }
 
     private void addToControlMaps(@NonNull InsetsControlTarget target,
-            @InternalInsetType int type) {
+            @InternalInsetType int type, boolean fake) {
         final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target,
                 key -> new ArrayList<>());
         array.add(type);
-        mTypeControlTargetMap.put(type, target);
+        if (fake) {
+            mTypeFakeControlTargetMap.put(type, target);
+        } else {
+            mTypeControlTargetMap.put(type, target);
+        }
     }
 
     void notifyControlChanged(InsetsControlTarget target) {
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 59df09b..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()
@@ -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/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/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index caf87cd..b30da5e 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -280,13 +280,6 @@
     }
 
     /**
-     * @return true if currently in the lock task mode, otherwise, return false.
-     */
-    boolean isInLockTaskMode() {
-        return !mLockTaskModeTasks.isEmpty();
-    }
-
-    /**
      * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(TaskRecord task) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index ef0049b..2e6df60 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();
@@ -90,8 +90,6 @@
     private boolean mIsMinimized;
     private boolean mIsImeShowing;
     private int mImeHeight;
-    private boolean mIsShelfShowing;
-    private int mShelfHeight;
 
     // The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
     private ArrayList<RemoteAction> mActions = new ArrayList<>();
@@ -106,9 +104,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 +113,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 +130,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 +185,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,8 +212,8 @@
             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
             // SystemUI may use the bounds to retore the minimized position
             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
@@ -238,58 +234,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);
@@ -303,21 +275,24 @@
                 mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
             } else {
                 Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
-                        0, Math.max(mIsImeShowing ? mImeHeight : 0,
-                                mIsShelfShowing ? mShelfHeight : 0),
-                        defaultBounds);
+                        0, mIsImeShowing ? mImeHeight : 0, defaultBounds);
             }
             return defaultBounds;
         }
     }
 
+    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 +310,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 +320,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.
@@ -386,28 +362,16 @@
     }
 
     /**
-     * Sets the shelf state and height.
-     */
-    void setAdjustedForShelf(boolean adjustedForShelf, int shelfHeight) {
-        final boolean shelfShowing = adjustedForShelf && shelfHeight > 0;
-        if (shelfShowing == mIsShelfShowing && shelfHeight == mShelfHeight) {
-            return;
-        }
-
-        mIsShelfShowing = shelfShowing;
-        mShelfHeight = shelfHeight;
-        notifyShelfVisibilityChanged(shelfShowing, shelfHeight);
-        notifyMovementBoundsChanged(false /* fromImeAdjustment */, true /* fromShelfAdjustment */);
-    }
-
-    /**
      * Sets the current aspect ratio.
      */
     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 +393,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);
@@ -451,13 +419,12 @@
         }
     }
 
-    private void notifyShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-        if (mPinnedStackListener != null) {
-            try {
-                mPinnedStackListener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
-            } catch (RemoteException e) {
-                Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
-            }
+    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);
         }
     }
 
@@ -497,23 +464,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 +478,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) {
@@ -602,9 +583,6 @@
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
         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 f4280a2..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);
     }
@@ -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 50b5902..734f224 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -193,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;
@@ -805,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);
@@ -815,6 +824,7 @@
             }
         } finally {
             mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
+            mInEnsureActivitiesVisible = false;
         }
     }
 
@@ -945,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();
@@ -960,10 +969,6 @@
         // 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
@@ -999,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
@@ -1615,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 */);
     }
 
     /**
@@ -1624,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;
@@ -1661,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);
@@ -2060,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);
@@ -2081,7 +2103,7 @@
                 }
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -2214,9 +2236,9 @@
     void getRunningTasks(int maxNum, List<ActivityManager.RunningTaskInfo> list,
             @WindowConfiguration.ActivityType int ignoreActivityType,
             @WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid,
-            boolean allowed, boolean crossUser) {
+            boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
         mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType,
-                ignoreWindowingMode, mActivityDisplays, callingUid, allowed, crossUser);
+                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 d0b6fc8..4365d03 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -472,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 22a9c32..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, boolean crossUser) {
+            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, crossUser);
+                        callingUid, allowed, crossUser, profileIds);
                 mTmpSortedSet.addAll(mTmpStackTasks);
             }
         }
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/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/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/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 d3f3981..0ea108e 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -545,7 +545,7 @@
     }
 
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (!isResizeable()) {
@@ -619,7 +619,7 @@
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             return kept;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -725,7 +725,7 @@
             windowManager.setWillReplaceWindow(topActivity.appToken, animate);
         }
 
-        windowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
@@ -809,7 +809,7 @@
                         !mightReplaceWindow, deferResume);
             }
         } finally {
-            windowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (mightReplaceWindow) {
@@ -1085,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) {
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/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index bef6a37..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();
-        }
     }
 
     /**
@@ -1008,27 +958,10 @@
 
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
-        if (mAnimationBackgroundSurface != null) {
-            mWmService.mTransactionFactory.get().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/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/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 4fce46b..c7916e8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -180,7 +180,6 @@
                     // Update animations of all applications, including those
                     // associated with exiting/removed apps
                     dc.updateWindowsForAnimator();
-                    dc.updateBackgroundForAnimator();
                     dc.prepareSurfaces();
                 }
 
@@ -306,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 e280a663..ec43ec5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -137,6 +137,14 @@
      */
     private boolean mCommittedReparentToAnimationLeash;
 
+    /**
+     * Callback which is triggered while changing the parent, after setting up the surface but
+     * before asking the parent to assign child layers.
+     */
+    interface PreAssignChildLayersCallback {
+        void onPreAssignChildLayers();
+    }
+
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
         mPendingTransaction = wms.mTransactionFactory.get();
@@ -176,6 +184,10 @@
      */
     @Override
     void onParentChanged() {
+        onParentChanged(null);
+    }
+
+    void onParentChanged(PreAssignChildLayersCallback callback) {
         super.onParentChanged();
         if (mParent == null) {
             return;
@@ -195,6 +207,10 @@
             reparentSurfaceControl(getPendingTransaction(), mParent.mSurfaceControl);
         }
 
+        if (callback != null) {
+            callback.onPreAssignChildLayers();
+        }
+
         // Either way we need to ask the parent to assign us a Z-order.
         mParent.assignChildLayers();
         scheduleAnimation();
@@ -895,6 +911,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 8d3a107..b4309c7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -248,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;
@@ -265,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;
@@ -284,8 +286,10 @@
 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;
 
@@ -297,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
@@ -314,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).
@@ -368,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})
@@ -399,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) {
@@ -1217,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);
@@ -1671,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();
@@ -2046,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();
 
@@ -2339,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;
 
@@ -2628,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
@@ -2775,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
@@ -2798,12 +2804,6 @@
         mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
     }
 
-    public boolean isKeyguardTrusted() {
-        synchronized (mGlobalLock) {
-            return mPolicy.isKeyguardTrustedLw();
-        }
-    }
-
     public void setKeyguardGoingAway(boolean keyguardGoingAway) {
         synchronized (mGlobalLock) {
             mKeyguardGoingAway = keyguardGoingAway;
@@ -2949,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")) {
@@ -2966,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,
@@ -5242,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(
@@ -5342,9 +5329,7 @@
     }
 
     void requestTraversal() {
-        synchronized (mGlobalLock) {
-            mWindowPlacerLocked.requestTraversal();
-        }
+        mWindowPlacerLocked.requestTraversal();
     }
 
     /** Note that Locked in this case is on mLayoutToAnim */
@@ -5609,16 +5594,6 @@
     }
 
     @Override
-    public void setShelfHeight(boolean visible, int shelfHeight) {
-        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.STATUS_BAR,
-                "setShelfHeight()");
-        synchronized (mGlobalLock) {
-            getDefaultDisplayContentLocked().getPinnedStackController().setAdjustedForShelf(visible,
-                    shelfHeight);
-        }
-    }
-
-    @Override
     public void statusBarVisibilityChanged(int displayId, int visibility) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
                 != PackageManager.PERMISSION_GRANTED) {
@@ -5812,55 +5787,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);
@@ -5887,6 +5813,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++) {
@@ -6282,6 +6213,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;
@@ -6343,6 +6277,10 @@
             if (dumpAll) {
                 pw.println(separator);
             }
+            dumpLogStatus(pw);
+            if (dumpAll) {
+                pw.println(separator);
+            }
             dumpHighRefreshRateBlacklist(pw);
         }
     }
@@ -6370,16 +6308,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;
@@ -6390,21 +6318,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);
     }
 
     /**
@@ -6452,19 +6377,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();
         }
     }
 
@@ -6486,11 +6409,9 @@
         }
     }
 
-    public void setDockedStackResizing(boolean resizing) {
-        synchronized (mGlobalLock) {
-            getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
-            requestTraversal();
-        }
+    void setDockedStackResizing(boolean resizing) {
+        getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+        requestTraversal();
     }
 
     @Override
@@ -7035,7 +6956,9 @@
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
-            requestTraversal();
+            synchronized (mGlobalLock) {
+                requestTraversal();
+            }
         }
 
         @Override
@@ -7451,6 +7374,11 @@
                         && configuration.touchscreen == Configuration.TOUCHSCREEN_FINGER;
             }
         }
+
+        @Override
+        public @Nullable KeyInterceptionInfo getKeyInterceptionInfoFromToken(IBinder inputToken) {
+            return mKeyInterceptionInfoForToken.get(inputToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7760,4 +7688,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/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 cbb0b3a..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;
@@ -520,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.
@@ -638,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) {
@@ -755,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) {
@@ -825,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);
     }
 
@@ -1125,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.
@@ -1280,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.
@@ -1979,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();
 
@@ -2008,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.
@@ -2220,6 +2220,7 @@
             mClientChannel.dispose();
             mClientChannel = null;
         }
+        mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
         mInputWindowHandle.token = null;
     }
 
@@ -4377,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;
 
@@ -5328,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 6dfbc36..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;
@@ -270,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();
@@ -428,10 +426,6 @@
         }
     }
 
-    private int getLayerStack() {
-        return mWin.getDisplayContent().getDisplay().getLayerStack();
-    }
-
     void resetDrawState() {
         mDrawState = DRAW_PENDING;
 
@@ -540,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");
@@ -552,7 +548,7 @@
 
         mLastHidden = true;
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Created surface " + this);
+        if (DEBUG) Slog.v(TAG, "Created surface " + this);
         return mSurfaceController;
     }
 
@@ -745,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;
@@ -762,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;
@@ -1068,8 +1065,7 @@
 
         if (mSurfaceResized) {
             mReportSurfaceResized = true;
-            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
-                    FINISH_LAYOUT_REDO_WALLPAPER);
+            mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
     }
 
@@ -1164,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/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 97b2047..d9c7fed 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -57,12 +57,15 @@
     ],
 
     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: [
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_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 478bc88..3734650 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -50,6 +50,7 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATION;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
 import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED;
@@ -1113,142 +1114,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);
@@ -1260,15 +1226,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();
@@ -1291,9 +1255,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);
@@ -1307,20 +1271,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);
@@ -1328,29 +1287,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);
@@ -1361,25 +1311,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)
@@ -1999,6 +1982,10 @@
             return LocalServices.getService(LockSettingsInternal.class);
         }
 
+        boolean hasUserSetupCompleted(DevicePolicyData userData) {
+            return userData.mUserSetupComplete;
+        }
+
         boolean isBuildDebuggable() {
             return Build.IS_DEBUGGABLE;
         }
@@ -5659,7 +5646,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) {
@@ -5830,6 +5817,8 @@
         idTypeToAttestationFlag.put(ID_TYPE_SERIAL, AttestationUtils.ID_TYPE_SERIAL);
         idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI);
         idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID);
+        idTypeToAttestationFlag.put(
+                ID_TYPE_INDIVIDUAL_ATTESTATION, AttestationUtils.USE_INDIVIDUAL_ATTESTATION);
 
         int numFlagsSet = Integer.bitCount(idAttestationFlags);
         // No flags are set - return null to indicate no device ID attestation information should
@@ -7403,8 +7392,7 @@
 
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
-            mInjector.getIActivityManager().requestBugReport(
-                    ActivityManager.BUGREPORT_OPTION_REMOTE);
+            mInjector.getIActivityManager().requestRemoteBugReport();
 
             mRemoteBugreportServiceIsActive.set(true);
             mRemoteBugreportSharingAccepted.set(false);
@@ -8271,7 +8259,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/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/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/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/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/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
new file mode 100644
index 0000000..ff03391
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/SystemConfigTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+/**
+ * Tests for {@link SystemConfig}.
+ *
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:SystemConfigTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemConfigTest {
+    private static final String LOG_TAG = "SystemConfigTest";
+
+    private SystemConfig mSysConfig;
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws Exception {
+        mSysConfig = new SystemConfigTestClass();
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass() {
+            super(false);
+        }
+    }
+
+    /**
+     * Tests that readPermissions works correctly for the tag: install-in-user-type
+     */
+    @Test
+    public void testInstallInUserType() throws Exception {
+        final String contents1 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package1\">\n"
+                + "        <install-in user-type=\"FULL\" />\n"
+                + "        <install-in user-type=\"PROFILE\" />\n"
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"FULL\" />\n"
+                + "        <install-in user-type=\"PROFILE\" />\n"
+                + "        <do-not-install-in user-type=\"GUEST\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        final String contents2 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"SYSTEM\" />\n"
+                + "        <do-not-install-in user-type=\"PROFILE\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        final String contents3 =
+                  "<permissions>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in invalid-attribute=\"ADMIN\" />\n" // Ignore invalid attribute
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type package=\"com.android.package2\">\n"
+                + "        <install-in user-type=\"RESTRICTED\" />\n"  // Valid
+                + "    </install-in-user-type>\n"
+                + "    <install-in-user-type>\n" // Ignored since missing package name
+                + "        <install-in user-type=\"ADMIN\" />\n"
+                + "    </install-in-user-type>\n"
+                + "</permissions>";
+
+        Map<String, Set<String>> expectedWhite = new ArrayMap<>();
+        expectedWhite.put("com.android.package1",
+                new ArraySet<>(Arrays.asList("FULL", "PROFILE")));
+        expectedWhite.put("com.android.package2",
+                new ArraySet<>(Arrays.asList("FULL", "PROFILE", "RESTRICTED", "SYSTEM")));
+
+        Map<String, Set<String>> expectedBlack = new ArrayMap<>();
+        expectedBlack.put("com.android.package2",
+                new ArraySet<>(Arrays.asList("GUEST", "PROFILE")));
+
+        final File folder1 = createTempSubfolder("folder1");
+        createTempFile(folder1, "permFile1.xml", contents1);
+
+        final File folder2 = createTempSubfolder("folder2");
+        createTempFile(folder2, "permFile2.xml", contents2);
+
+        // Also, make a third file, but with the name folder1/permFile2.xml, to prove no conflicts.
+        createTempFile(folder1, "permFile2.xml", contents3);
+
+        mSysConfig.readPermissions(folder1, /* No permission needed anyway */ 0);
+        mSysConfig.readPermissions(folder2, /* No permission needed anyway */ 0);
+
+        Map<String, Set<String>> actualWhite = mSysConfig.getAndClearPackageToUserTypeWhitelist();
+        Map<String, Set<String>> actualBlack = mSysConfig.getAndClearPackageToUserTypeBlacklist();
+
+        assertEquals("Whitelist was not cleared", 0,
+                mSysConfig.getAndClearPackageToUserTypeWhitelist().size());
+        assertEquals("Blacklist was not cleared", 0,
+                mSysConfig.getAndClearPackageToUserTypeBlacklist().size());
+
+        assertEquals("Incorrect whitelist.", expectedWhite, actualWhite);
+        assertEquals("Incorrect blacklist", expectedBlack, actualBlack);
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
+     * @return the folder
+     */
+    private File createTempSubfolder(String folderName)
+            throws IOException {
+        File folder = new File(mTemporaryFolder.getRoot(), folderName);
+        folder.mkdir();
+        return folder;
+    }
+
+    /**
+     * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
+     * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
+     * @param fileName name of the file (e.g. filename.xml) to create
+     * @param contents contents to write to the file
+     * @return the folder containing the newly created file (not the file itself!)
+     */
+    private File createTempFile(File folder, String fileName, String contents)
+            throws IOException {
+        File file = new File(folder, fileName);
+        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
+        bw.write(contents);
+        bw.close();
+
+        // Print to logcat for test debugging.
+        Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
+        Scanner input = new Scanner(file);
+        while (input.hasNextLine()) {
+            Log.d(LOG_TAG, input.nextLine());
+        }
+
+        return folder;
+    }
+}
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/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/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/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d6cb9826..d900910 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -765,7 +765,38 @@
     }
 
     /**
-     * Test for: @{link DevicePolicyManager#reportPasswordChanged}
+     * 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
diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java
new file mode 100644
index 0000000..9b76b13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.display.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AmbientFilterTest {
+    private ContextWrapper mContextSpy;
+    private Resources mResourcesSpy;
+    private static String TAG = "AmbientFilterTest";
+
+    @Before
+    public void setUp() throws Exception {
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+    }
+
+    @Test
+    public void testBrightnessFilter_ZeroIntercept() throws Exception {
+        final int horizon = 5 * 1000;
+        final int time_start = 30 * 1000;
+        final float intercept = 0.0f;
+        final int prediction_time = 100;  // Hardcoded in AmbientFilter: prediction of how long the
+                                          // latest prediction will last before a new prediction.
+        setMockValues(mResourcesSpy, horizon, intercept);
+        AmbientFilter filter = AmbientFilterFactory.createBrightnessFilter(TAG, mResourcesSpy);
+
+        // Add first value and verify
+        filter.addValue(time_start, 30);
+        assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
+
+        // Add second value and verify that they are being averaged:
+        filter.addValue(time_start + prediction_time, 40);
+        // We check immediately after the value is added to verify that the weight of the
+        // prediction time is being correctly applied to the recent value correctly.
+        // In this case (time is in seconds so 100ms = 0.1s):
+        //    weight 1 (w1) = (0.5*0.1^2 - 0.5*0^2) = 0.005
+        //    weight 2 (w2) = (0.5*0.2^2 - 0.5*0.1^2) = 0.015
+        //    w_t = w1 + w2 = 0.02
+        //    total = w1 * 30 + w2 * 40 = 0.75
+        //    estimate = total / w_t = 0.75 / 0.02 = 37.5
+        assertEquals(37.5, filter.getEstimate(time_start + prediction_time), 0.001);
+
+        // Add a third value to push the first value off of the buffer.
+        filter.addValue(time_start + horizon + prediction_time, 50);
+        assertEquals(40.38846f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
+    }
+
+    @Test
+    public void testBrightnessFilter_WithIntercept() throws Exception {
+        final int horizon = 5 * 1000;
+        final int time_start = 30 * 1000;
+        final float intercept = 10f;
+        final int prediction_time = 100;
+
+        setMockValues(mResourcesSpy, horizon, intercept);
+        AmbientFilter filter = AmbientFilterFactory.createBrightnessFilter(TAG, mResourcesSpy);
+
+        // Add first value and verify
+        filter.addValue(time_start, 30);
+        assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
+
+        // Add second value and verify that they are being averaged:
+        filter.addValue(time_start + prediction_time, 40);
+        // We check immediately after the value is added to verify that the weight of the
+        // prediction time is being correctly applied to the recent value correctly.
+        // In this case (time is in seconds so 100ms = 0.1s):
+        //    weight 1 (w1) = (0.5*0.1^2 + 0.1*100) - (0.5*0^2 + 0*100) = 1.005
+        //    weight 2 (w2) = (0.5*0.2^2 + 0.2*100) - (0.5*0.1^2 + 0.1*100) = 1.015
+        //    w_t = w1 + w2 = 2.02
+        //    total = w1 * 30 + w2 * 40 = 70.75
+        //    estimate = total / w_t = 70.75 / 2.02 = 35.024752475
+        assertEquals(35.02475f, filter.getEstimate(time_start + prediction_time), 0.001);
+
+        // Add a third value to push the first value off of the buffer.
+        filter.addValue(time_start + horizon + prediction_time, 50);
+        assertEquals(40.23513f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
+    }
+
+    private void setMockValues(Resources resources, int horizon, float intercept) {
+        doAnswer(invocation -> {
+            TypedValue value = (TypedValue) invocation.getArguments()[1];
+            value.type = TypedValue.TYPE_FLOAT;
+            value.data = Float.floatToRawIntBits(intercept);
+            return null;
+        }).when(mResourcesSpy).getValue(
+                eq(com.android.internal.R.dimen
+                .config_displayWhiteBalanceBrightnessFilterIntercept),
+                any(TypedValue.class), eq(true));
+        when(mResourcesSpy.getInteger(
+                com.android.internal.R.integer
+                .config_displayWhiteBalanceBrightnessFilterHorizon)).thenReturn(horizon);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
new file mode 100644
index 0000000..4d25510
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java
@@ -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 com.android.server.display.utils;
+
+public class AmbientFilterStubber extends AmbientFilter {
+    public AmbientFilterStubber() {
+        super(null, 1);
+    }
+
+    protected float filter(long time, RollingBuffer buffer) {
+        return 0f;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java
deleted file mode 100644
index 7816493..0000000
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterTest.java
+++ /dev/null
@@ -1,125 +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.display.whitebalance;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.content.ContextWrapper;
-import android.content.res.Resources;
-import android.util.TypedValue;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public final class AmbientFilterTest {
-    private ContextWrapper mContextSpy;
-    private Resources mResourcesSpy;
-
-    @Before
-    public void setUp() throws Exception {
-        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
-        mResourcesSpy = spy(mContextSpy.getResources());
-        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-    }
-
-    @Test
-    public void testBrightnessFilter_ZeroIntercept() throws Exception {
-        final int horizon = 5 * 1000;
-        final int time_start = 30 * 1000;
-        final float intercept = 0.0f;
-        final int prediction_time = 100;  // Hardcoded in AmbientFilter: prediction of how long the
-                                          // latest prediction will last before a new prediction.
-        setMockValues(mResourcesSpy, horizon, intercept);
-        AmbientFilter filter = DisplayWhiteBalanceFactory.createBrightnessFilter(mResourcesSpy);
-
-        // Add first value and verify
-        filter.addValue(time_start, 30);
-        assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
-
-        // Add second value and verify that they are being averaged:
-        filter.addValue(time_start + prediction_time, 40);
-        // We check immediately after the value is added to verify that the weight of the
-        // prediction time is being correctly applied to the recent value correctly.
-        // In this case (time is in seconds so 100ms = 0.1s):
-        //    weight 1 (w1) = (0.5*0.1^2 - 0.5*0^2) = 0.005
-        //    weight 2 (w2) = (0.5*0.2^2 - 0.5*0.1^2) = 0.015
-        //    w_t = w1 + w2 = 0.02
-        //    total = w1 * 30 + w2 * 40 = 0.75
-        //    estimate = total / w_t = 0.75 / 0.02 = 37.5
-        assertEquals(37.5, filter.getEstimate(time_start + prediction_time), 0.001);
-
-        // Add a third value to push the first value off of the buffer.
-        filter.addValue(time_start + horizon + prediction_time, 50);
-        assertEquals(40.38846f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
-    }
-
-    @Test
-    public void testBrightnessFilter_WithIntercept() throws Exception {
-        final int horizon = 5 * 1000;
-        final int time_start = 30 * 1000;
-        final float intercept = 10f;
-        final int prediction_time = 100;
-
-        setMockValues(mResourcesSpy, horizon, intercept);
-        AmbientFilter filter = DisplayWhiteBalanceFactory.createBrightnessFilter(mResourcesSpy);
-
-        // Add first value and verify
-        filter.addValue(time_start, 30);
-        assertEquals(30, filter.getEstimate(time_start + prediction_time), 0.001);
-
-        // Add second value and verify that they are being averaged:
-        filter.addValue(time_start + prediction_time, 40);
-        // We check immediately after the value is added to verify that the weight of the
-        // prediction time is being correctly applied to the recent value correctly.
-        // In this case (time is in seconds so 100ms = 0.1s):
-        //    weight 1 (w1) = (0.5*0.1^2 + 0.1*100) - (0.5*0^2 + 0*100) = 1.005
-        //    weight 2 (w2) = (0.5*0.2^2 + 0.2*100) - (0.5*0.1^2 + 0.1*100) = 1.015
-        //    w_t = w1 + w2 = 2.02
-        //    total = w1 * 30 + w2 * 40 = 70.75
-        //    estimate = total / w_t = 70.75 / 2.02 = 35.024752475
-        assertEquals(35.02475f, filter.getEstimate(time_start + prediction_time), 0.001);
-
-        // Add a third value to push the first value off of the buffer.
-        filter.addValue(time_start + horizon + prediction_time, 50);
-        assertEquals(40.23513f, filter.getEstimate(time_start + horizon + prediction_time), 0.001);
-    }
-
-    private void setMockValues(Resources resources, int horizon, float intercept) {
-        doAnswer(invocation -> {
-            TypedValue value = (TypedValue) invocation.getArguments()[1];
-            value.type = TypedValue.TYPE_FLOAT;
-            value.data = Float.floatToRawIntBits(intercept);
-            return null;
-        }).when(mResourcesSpy).getValue(
-                eq(com.android.internal.R.dimen
-                .config_displayWhiteBalanceBrightnessFilterIntercept),
-                any(TypedValue.class), eq(true));
-        when(mResourcesSpy.getInteger(
-                com.android.internal.R.integer
-                .config_displayWhiteBalanceBrightnessFilterHorizon)).thenReturn(horizon);
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
index 6b0798b..0d5a7d6 100644
--- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java
@@ -17,6 +17,8 @@
 package com.android.server.display.whitebalance;
 
 import com.android.internal.R;
+import com.android.server.display.utils.AmbientFilter;
+import com.android.server.display.utils.AmbientFilterStubber;
 import com.google.common.collect.ImmutableList;
 
 import static org.junit.Assert.assertEquals;
@@ -132,7 +134,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
             setEstimatedBrightnessAndUpdate(controller, luxOverride);
@@ -152,7 +154,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
             setEstimatedBrightnessAndUpdate(controller,
@@ -184,7 +186,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
             float luxOverride = mix(brightness0, brightness1, t);
@@ -221,7 +223,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         setEstimatedBrightnessAndUpdate(controller, 0.0f);
         assertEquals(controller.mPendingAmbientColorTemperature,
@@ -240,7 +242,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
         setEstimatedBrightnessAndUpdate(controller, luxOverride);
@@ -258,7 +260,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
         setEstimatedBrightnessAndUpdate(controller, luxOverride);
@@ -278,7 +280,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 8000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
             setEstimatedBrightnessAndUpdate(controller,
@@ -311,7 +313,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = 6000.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
             float luxOverride = mix(brightness0, brightness1, t);
@@ -350,7 +352,7 @@
                     DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
             final float ambientColorTemperature = 8000.0f;
             setEstimatedColorTemperature(controller, ambientColorTemperature);
-            controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+            controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
             for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) {
                 setEstimatedBrightnessAndUpdate(controller, luxOverride);
@@ -370,7 +372,7 @@
                 DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy);
         final float ambientColorTemperature = -1.0f;
         setEstimatedColorTemperature(controller, ambientColorTemperature);
-        controller.mBrightnessFilter = spy(controller.mBrightnessFilter);
+        controller.mBrightnessFilter = spy(new AmbientFilterStubber());
 
         for (float t = 0.0f; t <= 1.0f; t += 0.1f) {
             setEstimatedBrightnessAndUpdate(controller,
@@ -426,7 +428,7 @@
 
     private void setEstimatedColorTemperature(DisplayWhiteBalanceController controller,
                                               float ambientColorTemperature) {
-        AmbientFilter colorTemperatureFilter = spy(controller.mColorTemperatureFilter);
+        AmbientFilter colorTemperatureFilter = spy(new AmbientFilterStubber());
         controller.mColorTemperatureFilter = colorTemperatureFilter;
         when(colorTemperatureFilter.getEstimate(anyLong())).thenReturn(ambientColorTemperature);
     }
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 0f077f3..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,7 +54,6 @@
 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.PermissionSettings;
 
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/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
new file mode 100644
index 0000000..f0b0328
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_GUEST;
+import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
+import static android.content.pm.UserInfo.FLAG_SYSTEM;
+
+import static com.android.server.pm.UserSystemPackageInstaller.PACKAGE_WHITELIST_MODE_PROP;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST;
+import static com.android.server.pm.UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_LOG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for UserSystemPackageInstaller.
+ *
+ * <p>Run with:<pre>
+ * atest com.android.server.pm.UserSystemPackageInstallerTest
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserSystemPackageInstallerTest {
+    private static final String TAG = "UserSystemPackageInstallerTest";
+
+    private UserSystemPackageInstaller mUserSystemPackageInstaller;
+
+    private Context mContext;
+
+    /** Any users created during this test, for them to be removed when it's done. */
+    private final List<Integer> mRemoveUsers = new ArrayList<>();
+    /** Original value of PACKAGE_WHITELIST_MODE_PROP before the test, to reset at end. */
+    private final int mOriginalWhitelistMode = SystemProperties.getInt(
+            PACKAGE_WHITELIST_MODE_PROP, USER_TYPE_PACKAGE_WHITELIST_MODE_DEVICE_DEFAULT);
+
+    @Before
+    public void setup() {
+        // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+        // TODO: Remove once UMS supports proper dependency injection
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        UserManagerService ums = new UserManagerService(InstrumentationRegistry.getContext());
+
+        mUserSystemPackageInstaller = new UserSystemPackageInstaller(ums);
+        mContext = InstrumentationRegistry.getTargetContext();
+    }
+
+    @After
+    public void tearDown() {
+        UserManager um = UserManager.get(mContext);
+        for (int userId : mRemoveUsers) {
+            um.removeUser(userId);
+        }
+        setUserTypePackageWhitelistMode(mOriginalWhitelistMode);
+    }
+
+    /**
+     * Subclass of SystemConfig without running the constructor.
+     */
+    private class SystemConfigTestClass extends SystemConfig {
+        SystemConfigTestClass(boolean readPermissions) {
+            super(readPermissions);
+        }
+    }
+
+    /**
+     * Test that determineWhitelistedPackagesForUserTypes reads SystemConfig information properly.
+     */
+    @Test
+    public void testDetermineWhitelistedPackagesForUserTypes() {
+        SystemConfig sysConfig = new SystemConfigTestClass(false) {
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+                        "PROFILE", "SYSTEM", "GUEST", "FULL", "invalid-garbage1")));
+                r.put("com.android.package2", new ArraySet<>(Arrays.asList(
+                        "MANAGED_PROFILE")));
+                return r;
+            }
+
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList(
+                        "FULL", "RESTRICTED", "invalid-garbage2")));
+                return r;
+            }
+        };
+
+        final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+        expectedOutput.put("com.android.package1",
+                UserInfo.PROFILE_FLAGS_MASK | FLAG_SYSTEM | FLAG_GUEST);
+        expectedOutput.put("com.android.package2",
+                UserInfo.FLAG_MANAGED_PROFILE);
+
+        final ArrayMap<String, Integer> actualOutput =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+        assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+    }
+
+    /**
+     * Test that determineWhitelistedPackagesForUserTypes does not include packages that were never
+     * whitelisted properly, but does include packages that were whitelisted but then blacklisted.
+     */
+    @Test
+    public void testDetermineWhitelistedPackagesForUserTypes_noNetWhitelisting() {
+        SystemConfig sysConfig = new SystemConfigTestClass(false) {
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeWhitelist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                r.put("com.android.package1", new ArraySet<>(Arrays.asList("invalid1")));
+                // com.android.package2 has no whitelisting
+                r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+                r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE")));
+                r.put("com.android.package5", new ArraySet<>());
+                // com.android.package6 has no whitelisting
+                return r;
+            }
+
+            @Override
+            public ArrayMap<String, Set<String>> getAndClearPackageToUserTypeBlacklist() {
+                ArrayMap<String, Set<String>> r = new ArrayMap<>();
+                // com.android.package1 has no blacklisting
+                r.put("com.android.package2", new ArraySet<>(Arrays.asList("FULL")));
+                r.put("com.android.package3", new ArraySet<>(Arrays.asList("PROFILE", "FULL")));
+                r.put("com.android.package4", new ArraySet<>(Arrays.asList("PROFILE", "invalid4")));
+                // com.android.package5 has no blacklisting
+                r.put("com.android.package6", new ArraySet<>(Arrays.asList("invalid6")));
+                return r;
+            }
+        };
+
+        final ArrayMap<String, Integer> expectedOutput = getNewPackageToWhitelistedFlagsMap();
+        expectedOutput.put("com.android.package3", 0);
+        expectedOutput.put("com.android.package4", 0);
+
+        final ArrayMap<String, Integer> actualOutput =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+
+        assertEquals("Incorrect package-to-user mapping.", expectedOutput, actualOutput);
+    }
+
+    /**
+     * Tests that shouldInstallPackage correctly determines which packages should be installed.
+     */
+    @Test
+    public void testShouldInstallPackage() {
+        final String packageName1 = "pkg1"; // whitelisted
+        final String packageName2 = "pkg2"; // whitelisted and blacklisted
+        final String packageName3 = "pkg3"; // whitelisted for a different user type
+        final String packageName4 = "pkg4"; // not whitelisted nor blacklisted at all
+
+        final ArrayMap<String, Integer> pkgFlgMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+        pkgFlgMap.put(packageName1, FLAG_FULL);
+        pkgFlgMap.put(packageName2, 0);
+        pkgFlgMap.put(packageName3, FLAG_MANAGED_PROFILE);
+
+        // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+        final Set<String> userWhitelist = new ArraySet<>();
+        userWhitelist.add(packageName1);
+
+        final UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlgMap);
+
+        final PackageParser.Package pkg1 = new PackageParser.Package(packageName1);
+        final PackageParser.Package pkg2 = new PackageParser.Package(packageName2);
+        final PackageParser.Package pkg3 = new PackageParser.Package(packageName3);
+        final PackageParser.Package pkg4 = new PackageParser.Package(packageName4);
+
+        // No implicit whitelist, so only install pkg1.
+        boolean implicit = false;
+        boolean isSysUser = false;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+        // Use implicit whitelist, so install pkg1 and pkg4
+        implicit = true;
+        isSysUser = false;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+
+        // For user 0 specifically, we always implicitly whitelist.
+        implicit = false;
+        isSysUser = true;
+        assertTrue(uspi.shouldInstallPackage(pkg1, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg2, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertFalse(uspi.shouldInstallPackage(pkg3, pkgFlgMap, userWhitelist, implicit, isSysUser));
+        assertTrue(uspi.shouldInstallPackage(pkg4, pkgFlgMap, userWhitelist, implicit, isSysUser));
+    }
+
+    /**
+     * Tests that getWhitelistedPackagesForUserType works properly, assuming that
+     * mWhitelistedPackagesForUserTypes (i.e. determineWhitelistedPackagesForUserTypes) is correct.
+     */
+    @Test
+    public void testGetWhitelistedPackagesForUserType() {
+        final String packageName1 = "pkg1"; // whitelisted for FULL
+        final String packageName2 = "pkg2"; // blacklisted whenever whitelisted
+        final String packageName3 = "pkg3"; // whitelisted for SYSTEM
+        final String packageName4 = "pkg4"; // whitelisted for FULL
+
+        final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>(); // Whitelist: pkgs per flags
+        pkgFlagMap.put(packageName1, FLAG_FULL);
+        pkgFlagMap.put(packageName2, 0);
+        pkgFlagMap.put(packageName3, FLAG_SYSTEM);
+        pkgFlagMap.put(packageName4, FLAG_FULL);
+
+        // Whitelist of pkgs for this specific user, i.e. subset of pkgFlagMap for this user.
+        final Set<String> expectedUserWhitelist = new ArraySet<>();
+        expectedUserWhitelist.add(packageName1);
+
+        UserSystemPackageInstaller uspi = new UserSystemPackageInstaller(null, pkgFlagMap);
+
+        Set<String> output = uspi.getWhitelistedPackagesForUserType(FLAG_FULL);
+        assertEquals("Whitelist for FULL is the wrong size", 2, output.size());
+        assertTrue("Whitelist for FULL doesn't contain pkg1", output.contains(packageName1));
+        assertTrue("Whitelist for FULL doesn't contain pkg4", output.contains(packageName4));
+
+        output = uspi.getWhitelistedPackagesForUserType(FLAG_SYSTEM);
+        assertEquals("Whitelist for SYSTEM is the wrong size", 1, output.size());
+        assertTrue("Whitelist for SYSTEM doesn't contain pkg1", output.contains(packageName3));
+    }
+
+    /**
+     * Test that a newly created FULL user has the expected system packages.
+     *
+     * Assumes that SystemConfig and UserManagerService.determineWhitelistedPackagesForUserTypes
+     * work correctly (they are tested separately).
+     */
+    @Test
+    public void testPackagesForCreateUser_full() {
+        final int userFlags = UserInfo.FLAG_FULL;
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        PackageManager pm = mContext.getPackageManager();
+
+        final SystemConfig sysConfig = new SystemConfigTestClass(true);
+        final ArrayMap<String, Integer> packageMap =
+                mUserSystemPackageInstaller.determineWhitelistedPackagesForUserTypes(sysConfig);
+        final Set<String> expectedPackages = new ArraySet<>(packageMap.size());
+        for (int i = 0; i < packageMap.size(); i++) {
+            if ((userFlags & packageMap.valueAt(i)) != 0) {
+                expectedPackages.add(packageMap.keyAt(i));
+            }
+        }
+
+        final UserManager um = UserManager.get(mContext);
+        final UserInfo user = um.createUser("Test User", userFlags);
+        assertNotNull(user);
+        mRemoveUsers.add(user.id);
+
+        final List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(
+                PackageManager.MATCH_SYSTEM_ONLY
+                        | PackageManager.MATCH_DISABLED_COMPONENTS
+                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+                user.id);
+        final Set<String> actualPackages = new ArraySet<>(packageInfos.size());
+        for (PackageInfo p : packageInfos) {
+            actualPackages.add(p.packageName);
+        }
+        checkPackageDifferences(expectedPackages, actualPackages);
+    }
+
+    /** Asserts that actual is a subset of expected. */
+    private void checkPackageDifferences(Set<String> expected, Set<String> actual) {
+        final Set<String> uniqueToExpected = new ArraySet<>(expected);
+        uniqueToExpected.removeAll(actual);
+        final Set<String> uniqueToActual = new ArraySet<>(actual);
+        uniqueToActual.removeAll(expected);
+
+        Log.v(TAG, "Expected list uniquely has " + uniqueToExpected);
+        Log.v(TAG, "Actual list uniquely has " + uniqueToActual);
+
+        assertTrue("User's system packages includes non-whitelisted packages: " + uniqueToActual,
+                uniqueToActual.isEmpty());
+    }
+
+    /**
+     * Test that setEnableUserTypePackageWhitelist() has the correct effect.
+     */
+    @Test
+    public void testSetWhitelistEnabledMode() {
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_DISABLE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_LOG);
+        assertTrue(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertFalse(mUserSystemPackageInstaller.isEnforceMode());
+        assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(
+                USER_TYPE_PACKAGE_WHITELIST_MODE_LOG | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertTrue(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertFalse(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+
+        setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_IMPLICIT_WHITELIST
+                | USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE);
+        assertFalse(mUserSystemPackageInstaller.isLogMode());
+        assertTrue(mUserSystemPackageInstaller.isEnforceMode());
+        assertTrue(mUserSystemPackageInstaller.isImplicitWhitelistMode());
+    }
+
+    /** Sets the whitelist mode to the desired value via adb's setprop. */
+    private void setUserTypePackageWhitelistMode(int mode) {
+        UiDevice mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        try {
+            String result = mUiDevice.executeShellCommand(String.format("setprop %s %d",
+                    PACKAGE_WHITELIST_MODE_PROP, mode));
+            assertFalse("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ": " + result,
+                    result != null && result.contains("Failed"));
+        } catch (IOException e) {
+            fail("Failed to set sysprop " + PACKAGE_WHITELIST_MODE_PROP + ":\n" + e);
+        }
+    }
+
+    private ArrayMap<String, Integer> getNewPackageToWhitelistedFlagsMap() {
+        final ArrayMap<String, Integer> pkgFlagMap = new ArrayMap<>();
+        // "android" is always treated as whitelisted, regardless of the xml file.
+        pkgFlagMap.put("android", FLAG_SYSTEM | UserInfo.FLAG_FULL | UserInfo.PROFILE_FLAGS_MASK);
+        return pkgFlagMap;
+    }
+}
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..3e9f625
--- /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
index d27f1c7..b5925a6 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -18,11 +18,18 @@
 
 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 {
@@ -74,4 +81,62 @@
         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..dd2ee5c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.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.stats;
+
+import static com.android.server.stats.ProcfsMemoryUtil.parseMemorySnapshotFromStatus;
+
+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 testParseMemorySnapshotFromStatus_parsesCorrectValue() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus(STATUS_CONTENTS);
+        assertThat(snapshot.uid).isEqualTo(10083);
+        assertThat(snapshot.rssHighWaterMarkInKilobytes).isEqualTo(137668);
+        assertThat(snapshot.rssInKilobytes).isEqualTo(126776);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(37860);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(22);
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_invalidValue() {
+        MemorySnapshot snapshot =
+                parseMemorySnapshotFromStatus("test\nVmRSS:\tx0x0x\nVmSwap:\t1 kB\ntest");
+        assertThat(snapshot).isNull();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_emptyContents() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus("");
+        assertThat(snapshot).isNull();
+    }
+}
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/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 d11995a..2de8d05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -379,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;
@@ -422,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;
@@ -1064,6 +1079,20 @@
     }
 
     @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);
@@ -3275,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);
     }
@@ -4468,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)
@@ -4493,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);
 
@@ -4515,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);
 
@@ -4526,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 */);
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 8444ab2..30c8eb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -658,6 +658,7 @@
 
                     @Override
                     public void onAnimationStart(RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
                             IRemoteAnimationFinishedCallback finishedCallback) {
 
                     }
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 34cc0c7..1f672c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -354,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
@@ -366,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();
@@ -501,7 +501,6 @@
         final ActivityStarter starter = prepareStarter(0);
 
         final LockTaskController lockTaskController = mService.getLockTaskController();
-        doReturn(true).when(lockTaskController).isInLockTaskMode();
         doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
 
         final int result = starter.setReason("testTaskModeViolation").execute();
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 e6d7632..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;
@@ -140,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;
@@ -215,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++;
@@ -251,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
@@ -433,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/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/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
deleted file mode 100644
index efd468f..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ /dev/null
@@ -1,82 +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;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.view.IPinnedStackListener;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Build/Install/Run:
- *  atest FrameworksServicesTests:PinnedStackControllerTest
- */
-@SmallTest
-@Presubmit
-public class PinnedStackControllerTest extends WindowTestsBase {
-
-    private static final int SHELF_HEIGHT = 300;
-
-    @Mock private IPinnedStackListener mIPinnedStackListener;
-    @Mock private IPinnedStackListener.Stub mIPinnedStackListenerStub;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(mIPinnedStackListener.asBinder()).thenReturn(mIPinnedStackListenerStub);
-    }
-
-    @Test
-    public void setShelfHeight_shelfVisibilityChangedTriggered() throws RemoteException {
-        mWm.mAtmService.mSupportsPictureInPicture = true;
-        mWm.registerPinnedStackListener(DEFAULT_DISPLAY, mIPinnedStackListener);
-
-        verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(false), anyInt());
-        verify(mIPinnedStackListener).onActionsChanged(any());
-        verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
-
-        reset(mIPinnedStackListener);
-
-        mWm.setShelfHeight(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(true), anyInt());
-        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 55011fb..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 */);
@@ -1289,10 +1289,10 @@
         @Override
         void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
                 int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
-                int callingUid, boolean allowed, boolean crossUser) {
+                int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
             mLastAllowed = allowed;
             super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
-                    callingUid, allowed, crossUser);
+                    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/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 c67b860..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;
 
@@ -820,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 cdd4c24..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 */, true /*crossUser */);
+                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 */, true /* crossUser */);
+                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/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 43c6b35..5a4d399 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -74,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;
@@ -100,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;
@@ -229,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
@@ -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/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/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/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 090623e..e3183e3 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -340,6 +340,10 @@
             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) {
@@ -456,7 +460,17 @@
                     "usagestats");
             service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this);
             if (mUserUnlockedStates.get(userId)) {
-                service.init(currentTimeMillis);
+                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);
         }
@@ -779,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);
@@ -841,6 +858,9 @@
 
             final UserUsageStatsService service =
                     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;
@@ -873,6 +893,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryConfigurationStats(bucketType, beginTime, endTime);
         }
     }
@@ -890,6 +913,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventStats(bucketType, beginTime, endTime);
         }
     }
@@ -907,6 +933,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
         }
     }
@@ -924,6 +953,9 @@
 
             final UserUsageStatsService service =
                     getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
+            if (service == null) {
+                return null; // user was stopped or removed
+            }
             return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
@@ -1113,7 +1145,15 @@
                     flushToDisk();
                     break;
                 case MSG_UNLOCKED_USER:
-                    onUserUnlocked(msg.arg1);
+                    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);
@@ -1986,6 +2026,9 @@
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
                             user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return null; // user was stopped or removed
+                    }
                     return userStats.getBackupPayload(key);
                 } else {
                     return null;
@@ -2004,6 +2047,9 @@
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
                             user, System.currentTimeMillis());
+                    if (userStats == null) {
+                        return; // user was stopped or removed
+                    }
                     userStats.applyRestoredPayload(key, payload);
                 }
             }
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index c8c55ca..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,6 +352,67 @@
         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 be called by owner.
@@ -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 be32c86..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,78 @@
     }
 
     @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);
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/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/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 13b7b5c..a806320 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,6 +17,7 @@
 android_app {
     name: "startop_test_app",
     srcs: [
+        "src/CPUIntensive.java",
         "src/EmptyActivity.java",
         "src/LayoutInflationActivity.java",
         "src/ComplexLayoutInflationActivity.java",
@@ -24,5 +25,5 @@
         "src/SystemServerBenchmarkActivity.java",
         "src/TextViewInflationActivity.java",
     ],
-    platform_apis: true,
+    sdk_version: "26", // Android O (8.0) and higher
 }
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
index 61d4322..c8d9fde 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -17,22 +17,20 @@
 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.os.Trace;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.GridLayout;
 import android.widget.TextView;
 
-import java.util.Arrays;
 
 class Benchmark {
     // Time limit to run benchmarks in seconds
@@ -105,6 +103,22 @@
         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);
@@ -139,6 +153,10 @@
                     throw new RuntimeException(e);
                 }
             });
+
+            new Benchmark(benchmarkList, "getPackagesForUid", () -> {
+                pm.getPackagesForUid(app.uid);
+            });
         } catch (NameNotFoundException e) {
             throw new RuntimeException(e);
         }
@@ -152,5 +170,45 @@
             }
         });
 
+        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/scripts/iorap/compiler_ri.py b/startop/scripts/iorap/compiler_ri.py
index 17b58c1..90fc8a8 100755
--- a/startop/scripts/iorap/compiler_ri.py
+++ b/startop/scripts/iorap/compiler_ri.py
@@ -139,7 +139,9 @@
       continue
 
     file_id = file_id_map.get(filename)
-    if not file_id:
+    # 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
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.cc b/startop/view_compiler/dex_builder.cc
index 499c42e..48b44d0 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -161,7 +161,7 @@
 
   MethodBuilder method{cbuilder.CreateMethod("foo", Prototype{TypeDescriptor::Int(), string_type})};
 
-  Value result = method.MakeRegister();
+  LiveRegister result = method.AllocRegister();
 
   MethodDeclData string_length =
       dex_file.GetOrDeclareMethod(string_type, "length", Prototype{TypeDescriptor::Int()});
@@ -314,7 +314,7 @@
   CHECK(decl_->prototype != nullptr);
   size_t const num_args =
       decl_->prototype->param_types != nullptr ? decl_->prototype->param_types->types.size() : 0;
-  code->registers = num_registers_ + num_args + kMaxScratchRegisters;
+  code->registers = NumRegisters() + num_args + kMaxScratchRegisters;
   code->ins_count = num_args;
   EncodeInstructions();
   code->instructions = slicer::ArrayView<const ::dex::u2>(buffer_.data(), buffer_.size());
@@ -327,7 +327,20 @@
   return method;
 }
 
-Value MethodBuilder::MakeRegister() { return Value::Local(num_registers_++); }
+LiveRegister MethodBuilder::AllocRegister() {
+  // Find a free register
+  for (size_t i = 0; i < register_liveness_.size(); ++i) {
+    if (!register_liveness_[i]) {
+      register_liveness_[i] = true;
+      return LiveRegister{&register_liveness_, i};
+    }
+  }
+
+  // If we get here, all the registers are in use, so we have to allocate a new
+  // one.
+  register_liveness_.push_back(true);
+  return LiveRegister{&register_liveness_, register_liveness_.size() - 1};
+}
 
 Value MethodBuilder::MakeLabel() {
   labels_.push_back({});
@@ -600,7 +613,7 @@
   if (value.is_register()) {
     return value.value();
   } else if (value.is_parameter()) {
-    return value.value() + num_registers_ + kMaxScratchRegisters;
+    return value.value() + NumRegisters() + kMaxScratchRegisters;
   }
   CHECK(false && "Must be either a parameter or a register");
   return 0;
diff --git a/startop/view_compiler/dex_builder.h b/startop/view_compiler/dex_builder.h
index 292d659..3924e77 100644
--- a/startop/view_compiler/dex_builder.h
+++ b/startop/view_compiler/dex_builder.h
@@ -140,6 +140,29 @@
   constexpr Value(size_t value, Kind kind) : value_{value}, kind_{kind} {}
 };
 
+// Represents an allocated register returned by MethodBuilder::AllocRegister
+class LiveRegister {
+  friend class MethodBuilder;
+
+ public:
+  LiveRegister(LiveRegister&& other) : liveness_{other.liveness_}, index_{other.index_} {
+    other.index_ = {};
+  };
+  ~LiveRegister() {
+    if (index_.has_value()) {
+      (*liveness_)[*index_] = false;
+    }
+  };
+
+  operator const Value() const { return Value::Local(*index_); }
+
+ private:
+  LiveRegister(std::vector<bool>* liveness, size_t index) : liveness_{liveness}, index_{index} {}
+
+  std::vector<bool>* const liveness_;
+  std::optional<size_t> index_;
+};
+
 // A virtual instruction. We convert these to real instructions in MethodBuilder::Encode.
 // Virtual instructions are needed to keep track of information that is not known until all of the
 // code is generated. This information includes things like how many local registers are created and
@@ -178,7 +201,8 @@
   }
   // For most instructions, which take some number of arguments and have an optional return value.
   template <typename... T>
-  static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest, T... args) {
+  static inline Instruction OpWithArgs(Op opcode, std::optional<const Value> dest,
+                                       const T&... args) {
     return Instruction{opcode, /*index_argument=*/0, /*result_is_object=*/false, dest, args...};
   }
 
@@ -199,14 +223,14 @@
   template <typename... T>
   static inline Instruction InvokeVirtualObject(size_t index_argument,
                                                 std::optional<const Value> dest, Value this_arg,
-                                                T... args) {
+                                                const T&... args) {
     return Instruction{
         Op::kInvokeVirtual, index_argument, /*result_is_object=*/true, dest, this_arg, args...};
   }
   // For direct calls (basically, constructors).
   template <typename... T>
   static inline Instruction InvokeDirect(size_t index_argument, std::optional<const Value> dest,
-                                         Value this_arg, T... args) {
+                                         Value this_arg, const T&... args) {
     return Instruction{
         Op::kInvokeDirect, index_argument, /*result_is_object=*/false, dest, this_arg, args...};
   }
@@ -234,7 +258,7 @@
   // For static calls.
   template <typename... T>
   static inline Instruction InvokeInterface(size_t index_argument, std::optional<const Value> dest,
-                                            T... args) {
+                                            const T&... args) {
     return Instruction{
         Op::kInvokeInterface, index_argument, /*result_is_object=*/false, dest, args...};
   }
@@ -277,7 +301,7 @@
 
   template <typename... T>
   inline Instruction(Op opcode, size_t index_argument, bool result_is_object,
-                               std::optional<const Value> dest, T... args)
+                     std::optional<const Value> dest, const T&... args)
       : opcode_{opcode},
         index_argument_{index_argument},
         result_is_object_{result_is_object},
@@ -309,10 +333,8 @@
   // Encode the method into DEX format.
   ir::EncodedMethod* Encode();
 
-  // Create a new register to be used to storing values. Note that these are not SSA registers, like
-  // might be expected in similar code generators. This does no liveness tracking or anything, so
-  // it's up to the caller to reuse registers as appropriate.
-  Value MakeRegister();
+  // Create a new register to be used to storing values.
+  LiveRegister AllocRegister();
 
   Value MakeLabel();
 
@@ -329,7 +351,7 @@
   void BuildConst4(Value target, int value);
   void BuildConstString(Value target, const std::string& value);
   template <typename... T>
-  void BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args);
+  void BuildNew(Value target, TypeDescriptor type, Prototype constructor, const T&... args);
 
   // TODO: add builders for more instructions
 
@@ -427,7 +449,7 @@
     static_assert(num_regs <= kMaxScratchRegisters);
     std::array<Value, num_regs> regs;
     for (size_t i = 0; i < num_regs; ++i) {
-      regs[i] = std::move(Value::Local(num_registers_ + i));
+      regs[i] = std::move(Value::Local(NumRegisters() + i));
     }
     return regs;
   }
@@ -457,8 +479,9 @@
   // around to make legal DEX code.
   static constexpr size_t kMaxScratchRegisters = 5;
 
-  // How many registers we've allocated
-  size_t num_registers_{0};
+  size_t NumRegisters() const {
+    return register_liveness_.size();
+  }
 
   // Stores information needed to back-patch a label once it is bound. We need to know the start of
   // the instruction that refers to the label, and the offset to where the actual label value should
@@ -478,6 +501,8 @@
   // During encoding, keep track of the largest number of arguments needed, so we can use it for our
   // outs count
   size_t max_args_{0};
+
+  std::vector<bool> register_liveness_;
 };
 
 // A helper to build class definitions.
@@ -576,7 +601,8 @@
 };
 
 template <typename... T>
-void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor, T... args) {
+void MethodBuilder::BuildNew(Value target, TypeDescriptor type, Prototype constructor,
+                             const T&... args) {
   MethodDeclData constructor_data{dex_->GetOrDeclareMethod(type, "<init>", constructor)};
   // allocate the object
   ir::Type* type_def = dex_->GetOrAddType(type.descriptor());
diff --git a/startop/view_compiler/dex_layout_compiler.cc b/startop/view_compiler/dex_layout_compiler.cc
index 8febfb7..cb820f8 100644
--- a/startop/view_compiler/dex_layout_compiler.cc
+++ b/startop/view_compiler/dex_layout_compiler.cc
@@ -22,76 +22,94 @@
 namespace startop {
 
 using android::base::StringPrintf;
+using dex::Instruction;
+using dex::LiveRegister;
+using dex::Prototype;
+using dex::TypeDescriptor;
+using dex::Value;
+
+namespace {
+// TODO: these are a bunch of static initializers, which we should avoid. See if
+// we can make them constexpr.
+const TypeDescriptor kAttributeSet = TypeDescriptor::FromClassname("android.util.AttributeSet");
+const TypeDescriptor kContext = TypeDescriptor::FromClassname("android.content.Context");
+const TypeDescriptor kLayoutInflater = TypeDescriptor::FromClassname("android.view.LayoutInflater");
+const TypeDescriptor kResources = TypeDescriptor::FromClassname("android.content.res.Resources");
+const TypeDescriptor kString = TypeDescriptor::FromClassname("java.lang.String");
+const TypeDescriptor kView = TypeDescriptor::FromClassname("android.view.View");
+const TypeDescriptor kViewGroup = TypeDescriptor::FromClassname("android.view.ViewGroup");
+const TypeDescriptor kXmlResourceParser =
+    TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
+}  // namespace
 
 DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
     : method_{method},
-      context_{dex::Value::Parameter(0)},
-      resid_{dex::Value::Parameter(1)},
-      inflater_{method->MakeRegister()},
-      xml_{method->MakeRegister()},
-      attrs_{method->MakeRegister()},
-      classname_tmp_{method->MakeRegister()},
-      xml_next_{method->dex_file()->GetOrDeclareMethod(
-          dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"), "next",
-          dex::Prototype{dex::TypeDescriptor::Int()})},
+      context_{Value::Parameter(0)},
+      resid_{Value::Parameter(1)},
+      inflater_{method->AllocRegister()},
+      xml_{method->AllocRegister()},
+      attrs_{method->AllocRegister()},
+      classname_tmp_{method->AllocRegister()},
+      xml_next_{method->dex_file()->GetOrDeclareMethod(kXmlResourceParser, "next",
+                                                       Prototype{TypeDescriptor::Int()})},
       try_create_view_{method->dex_file()->GetOrDeclareMethod(
-          dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), "tryCreateView",
-          dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
-                         dex::TypeDescriptor::FromClassname("android.view.View"),
-                         dex::TypeDescriptor::FromClassname("java.lang.String"),
-                         dex::TypeDescriptor::FromClassname("android.content.Context"),
-                         dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
+          kLayoutInflater, "tryCreateView",
+          Prototype{kView, kView, kString, kContext, kAttributeSet})},
       generate_layout_params_{method->dex_file()->GetOrDeclareMethod(
-          dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "generateLayoutParams",
-          dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
-                         dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
+          kViewGroup, "generateLayoutParams",
+          Prototype{TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
+                    kAttributeSet})},
       add_view_{method->dex_file()->GetOrDeclareMethod(
-          dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "addView",
-          dex::Prototype{
-              dex::TypeDescriptor::Void(),
-              dex::TypeDescriptor::FromClassname("android.view.View"),
-              dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})},
-      // The register stack starts with one register, which will be null for the root view.
-      register_stack_{{method->MakeRegister()}} {}
+          kViewGroup, "addView",
+          Prototype{TypeDescriptor::Void(),
+                    kView,
+                    TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})} {}
+
+void DexViewBuilder::BuildGetLayoutInflater(Value dest) {
+  // dest = LayoutInflater.from(context);
+  auto layout_inflater_from = method_->dex_file()->GetOrDeclareMethod(
+      kLayoutInflater, "from", Prototype{kLayoutInflater, kContext});
+  method_->AddInstruction(Instruction::InvokeStaticObject(layout_inflater_from.id, dest, context_));
+}
+
+void DexViewBuilder::BuildGetResources(Value dest) {
+  // dest = context.getResources();
+  auto get_resources =
+      method_->dex_file()->GetOrDeclareMethod(kContext, "getResources", Prototype{kResources});
+  method_->AddInstruction(Instruction::InvokeVirtualObject(get_resources.id, dest, context_));
+}
+
+void DexViewBuilder::BuildGetLayoutResource(Value dest, Value resources, Value resid) {
+  // dest = resources.getLayout(resid);
+  auto get_layout = method_->dex_file()->GetOrDeclareMethod(
+      kResources, "getLayout", Prototype{kXmlResourceParser, TypeDescriptor::Int()});
+  method_->AddInstruction(Instruction::InvokeVirtualObject(get_layout.id, dest, resources, resid));
+}
+
+void DexViewBuilder::BuildLayoutResourceToAttributeSet(dex::Value dest,
+                                                       dex::Value layout_resource) {
+  // dest = Xml.asAttributeSet(layout_resource);
+  auto as_attribute_set = method_->dex_file()->GetOrDeclareMethod(
+      TypeDescriptor::FromClassname("android.util.Xml"),
+      "asAttributeSet",
+      Prototype{kAttributeSet, TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
+  method_->AddInstruction(
+      Instruction::InvokeStaticObject(as_attribute_set.id, dest, layout_resource));
+}
+
+void DexViewBuilder::BuildXmlNext() {
+  // xml_.next();
+  method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+}
 
 void DexViewBuilder::Start() {
-  dex::DexBuilder* const dex = method_->dex_file();
+  BuildGetLayoutInflater(/*dest=*/inflater_);
+  BuildGetResources(/*dest=*/xml_);
+  BuildGetLayoutResource(/*dest=*/xml_, /*resources=*/xml_, resid_);
+  BuildLayoutResourceToAttributeSet(/*dest=*/attrs_, /*layout_resource=*/xml_);
 
-  // LayoutInflater inflater = LayoutInflater.from(context);
-  auto layout_inflater_from = dex->GetOrDeclareMethod(
-      dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
-      "from",
-      dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
-                     dex::TypeDescriptor::FromClassname("android.content.Context")});
-  method_->AddInstruction(
-      dex::Instruction::InvokeStaticObject(layout_inflater_from.id, /*dest=*/inflater_, context_));
-
-  // Resources res = context.getResources();
-  auto context_type = dex::TypeDescriptor::FromClassname("android.content.Context");
-  auto resources_type = dex::TypeDescriptor::FromClassname("android.content.res.Resources");
-  auto get_resources =
-      dex->GetOrDeclareMethod(context_type, "getResources", dex::Prototype{resources_type});
-  method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_resources.id, xml_, context_));
-
-  // XmlResourceParser xml = res.getLayout(resid);
-  auto xml_resource_parser_type =
-      dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
-  auto get_layout =
-      dex->GetOrDeclareMethod(resources_type,
-                              "getLayout",
-                              dex::Prototype{xml_resource_parser_type, dex::TypeDescriptor::Int()});
-  method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_layout.id, xml_, xml_, resid_));
-
-  // AttributeSet attrs = Xml.asAttributeSet(xml);
-  auto as_attribute_set = dex->GetOrDeclareMethod(
-      dex::TypeDescriptor::FromClassname("android.util.Xml"),
-      "asAttributeSet",
-      dex::Prototype{dex::TypeDescriptor::FromClassname("android.util.AttributeSet"),
-                     dex::TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
-  method_->AddInstruction(dex::Instruction::InvokeStaticObject(as_attribute_set.id, attrs_, xml_));
-
-  // xml.next(); // start document
-  method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+  // Advance past start document tag
+  BuildXmlNext();
 }
 
 void DexViewBuilder::Finish() {}
@@ -107,58 +125,57 @@
 }
 }  // namespace
 
+void DexViewBuilder::BuildTryCreateView(Value dest, Value parent, Value classname) {
+  // dest = inflater_.tryCreateView(parent, classname, context_, attrs_);
+  method_->AddInstruction(Instruction::InvokeVirtualObject(
+      try_create_view_.id, dest, inflater_, parent, classname, context_, attrs_));
+}
+
 void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) {
   bool const is_root_view = view_stack_.empty();
 
-  // xml.next(); // start tag
-  method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+  // Advance to start tag
+  BuildXmlNext();
 
-  dex::Value view = AcquireRegister();
+  LiveRegister view = AcquireRegister();
   // try to create the view using the factories
   method_->BuildConstString(classname_tmp_,
                             name);  // TODO: the need to fully qualify the classname
   if (is_root_view) {
-    dex::Value null = AcquireRegister();
+    LiveRegister null = AcquireRegister();
     method_->BuildConst4(null, 0);
-    method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
-        try_create_view_.id, view, inflater_, null, classname_tmp_, context_, attrs_));
-    ReleaseRegister();
+    BuildTryCreateView(/*dest=*/view, /*parent=*/null, classname_tmp_);
   } else {
-    method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
-        try_create_view_.id, view, inflater_, GetCurrentView(), classname_tmp_, context_, attrs_));
+    BuildTryCreateView(/*dest=*/view, /*parent=*/GetCurrentView(), classname_tmp_);
   }
   auto label = method_->MakeLabel();
   // branch if not null
   method_->AddInstruction(
-      dex::Instruction::OpWithArgs(dex::Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
+      Instruction::OpWithArgs(Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
 
   // If null, create the class directly.
   method_->BuildNew(view,
-                    dex::TypeDescriptor::FromClassname(ResolveName(name)),
-                    dex::Prototype{dex::TypeDescriptor::Void(),
-                                   dex::TypeDescriptor::FromClassname("android.content.Context"),
-                                   dex::TypeDescriptor::FromClassname("android.util.AttributeSet")},
+                    TypeDescriptor::FromClassname(ResolveName(name)),
+                    Prototype{TypeDescriptor::Void(), kContext, kAttributeSet},
                     context_,
                     attrs_);
 
-  method_->AddInstruction(
-      dex::Instruction::OpWithArgs(dex::Instruction::Op::kBindLabel, /*dest=*/{}, label));
+  method_->AddInstruction(Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, label));
 
   if (is_viewgroup) {
     // Cast to a ViewGroup so we can add children later.
-    const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(
-        dex::TypeDescriptor::FromClassname("android.view.ViewGroup").descriptor());
-    method_->AddInstruction(dex::Instruction::Cast(view, dex::Value::Type(view_group_def->orig_index)));
+    const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(kViewGroup.descriptor());
+    method_->AddInstruction(Instruction::Cast(view, Value::Type(view_group_def->orig_index)));
   }
 
   if (!is_root_view) {
     // layout_params = parent.generateLayoutParams(attrs);
-    dex::Value layout_params{AcquireRegister()};
-    method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
+    LiveRegister layout_params{AcquireRegister()};
+    method_->AddInstruction(Instruction::InvokeVirtualObject(
         generate_layout_params_.id, layout_params, GetCurrentView(), attrs_));
-    view_stack_.push_back({view, layout_params});
+    view_stack_.push_back({std::move(view), std::move(layout_params)});
   } else {
-    view_stack_.push_back({view, {}});
+    view_stack_.push_back({std::move(view), {}});
   }
 }
 
@@ -167,40 +184,24 @@
     method_->BuildReturn(GetCurrentView(), /*is_object=*/true);
   } else {
     // parent.add(view, layout_params)
-    method_->AddInstruction(dex::Instruction::InvokeVirtual(
+    method_->AddInstruction(Instruction::InvokeVirtual(
         add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams()));
     // xml.next(); // end tag
-    method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
+    method_->AddInstruction(Instruction::InvokeInterface(xml_next_.id, {}, xml_));
   }
   PopViewStack();
 }
 
-dex::Value DexViewBuilder::AcquireRegister() {
-  top_register_++;
-  if (register_stack_.size() == top_register_) {
-    register_stack_.push_back(method_->MakeRegister());
-  }
-  return register_stack_[top_register_];
-}
+LiveRegister DexViewBuilder::AcquireRegister() { return method_->AllocRegister(); }
 
-void DexViewBuilder::ReleaseRegister() { top_register_--; }
-
-dex::Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
-dex::Value DexViewBuilder::GetCurrentLayoutParams() const {
+Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
+Value DexViewBuilder::GetCurrentLayoutParams() const {
   return view_stack_.back().layout_params.value();
 }
-dex::Value DexViewBuilder::GetParentView() const {
-  return view_stack_[view_stack_.size() - 2].view;
-}
+Value DexViewBuilder::GetParentView() const { return view_stack_[view_stack_.size() - 2].view; }
 
 void DexViewBuilder::PopViewStack() {
-  const auto& top = view_stack_.back();
-  // release the layout params if we have them
-  if (top.layout_params.has_value()) {
-    ReleaseRegister();
-  }
   // Unconditionally release the view register.
-  ReleaseRegister();
   view_stack_.pop_back();
 }
 
diff --git a/startop/view_compiler/dex_layout_compiler.h b/startop/view_compiler/dex_layout_compiler.h
index 170a1a6..a34ed1f 100644
--- a/startop/view_compiler/dex_layout_compiler.h
+++ b/startop/view_compiler/dex_layout_compiler.h
@@ -79,36 +79,41 @@
 
  private:
   // Accessors for the stack of views that are under construction.
-  dex::Value AcquireRegister();
-  void ReleaseRegister();
+  dex::LiveRegister AcquireRegister();
   dex::Value GetCurrentView() const;
   dex::Value GetCurrentLayoutParams() const;
   dex::Value GetParentView() const;
   void PopViewStack();
 
+  // Methods to simplify building different code fragments.
+  void BuildGetLayoutInflater(dex::Value dest);
+  void BuildGetResources(dex::Value dest);
+  void BuildGetLayoutResource(dex::Value dest, dex::Value resources, dex::Value resid);
+  void BuildLayoutResourceToAttributeSet(dex::Value dest, dex::Value layout_resource);
+  void BuildXmlNext();
+  void BuildTryCreateView(dex::Value dest, dex::Value parent, dex::Value classname);
+
   dex::MethodBuilder* method_;
 
-  // Registers used for code generation
+  // Parameters to the generated method
   dex::Value const context_;
   dex::Value const resid_;
-  const dex::Value inflater_;
-  const dex::Value xml_;
-  const dex::Value attrs_;
-  const dex::Value classname_tmp_;
+
+  // Registers used for code generation
+  const dex::LiveRegister inflater_;
+  const dex::LiveRegister xml_;
+  const dex::LiveRegister attrs_;
+  const dex::LiveRegister classname_tmp_;
 
   const dex::MethodDeclData xml_next_;
   const dex::MethodDeclData try_create_view_;
   const dex::MethodDeclData generate_layout_params_;
   const dex::MethodDeclData add_view_;
 
-  // used for keeping track of which registers are in use
-  size_t top_register_{0};
-  std::vector<dex::Value> register_stack_;
-
   // Keep track of the views currently in progress.
   struct ViewEntry {
-    dex::Value view;
-    std::optional<dex::Value> layout_params;
+    dex::LiveRegister view;
+    std::optional<dex::LiveRegister> layout_params;
   };
   std::vector<ViewEntry> view_stack_;
 };
diff --git a/startop/view_compiler/dex_testcase_generator.cc b/startop/view_compiler/dex_testcase_generator.cc
index 6dedf24..5dda59e 100644
--- a/startop/view_compiler/dex_testcase_generator.cc
+++ b/startop/view_compiler/dex_testcase_generator.cc
@@ -47,7 +47,7 @@
   // int return5() { return 5; }
   auto return5{cbuilder.CreateMethod("return5", Prototype{TypeDescriptor::Int()})};
   {
-    Value r{return5.MakeRegister()};
+    LiveRegister r{return5.AllocRegister()};
     return5.BuildConst4(r, 5);
     return5.BuildReturn(r);
   }
@@ -57,9 +57,9 @@
   auto integer_type{TypeDescriptor::FromClassname("java.lang.Integer")};
   auto returnInteger5{cbuilder.CreateMethod("returnInteger5", Prototype{integer_type})};
   [&](MethodBuilder& method) {
-    Value five{method.MakeRegister()};
+    LiveRegister five{method.AllocRegister()};
     method.BuildConst4(five, 5);
-    Value object{method.MakeRegister()};
+    LiveRegister object{method.AllocRegister()};
     method.BuildNew(
         object, integer_type, Prototype{TypeDescriptor::Void(), TypeDescriptor::Int()}, five);
     method.BuildReturn(object, /*is_object=*/true);
@@ -80,7 +80,7 @@
   auto returnStringLength{
       cbuilder.CreateMethod("returnStringLength", Prototype{TypeDescriptor::Int(), string_type})};
   {
-    Value result = returnStringLength.MakeRegister();
+    LiveRegister result = returnStringLength.AllocRegister();
     returnStringLength.AddInstruction(
         Instruction::InvokeVirtual(string_length.id, result, Value::Parameter(0)));
     returnStringLength.BuildReturn(result);
@@ -91,7 +91,7 @@
   MethodBuilder returnIfZero{cbuilder.CreateMethod(
       "returnIfZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
   {
-    Value resultIfZero{returnIfZero.MakeRegister()};
+    LiveRegister resultIfZero{returnIfZero.AllocRegister()};
     Value else_target{returnIfZero.MakeLabel()};
     returnIfZero.AddInstruction(Instruction::OpWithArgs(
         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
@@ -112,7 +112,7 @@
   MethodBuilder returnIfNotZero{cbuilder.CreateMethod(
       "returnIfNotZero", Prototype{TypeDescriptor::Int(), TypeDescriptor::Int()})};
   {
-    Value resultIfNotZero{returnIfNotZero.MakeRegister()};
+    LiveRegister resultIfNotZero{returnIfNotZero.AllocRegister()};
     Value else_target{returnIfNotZero.MakeLabel()};
     returnIfNotZero.AddInstruction(Instruction::OpWithArgs(
         Instruction::Op::kBranchNEqz, /*dest=*/{}, Value::Parameter(0), else_target));
@@ -148,8 +148,8 @@
   MethodBuilder backwardsBranch{
       cbuilder.CreateMethod("backwardsBranch", Prototype{TypeDescriptor::Int()})};
   [](MethodBuilder& method) {
-    Value zero = method.MakeRegister();
-    Value result = method.MakeRegister();
+    LiveRegister zero = method.AllocRegister();
+    LiveRegister result = method.AllocRegister();
     Value labelA = method.MakeLabel();
     Value labelB = method.MakeLabel();
     method.BuildConst4(zero, 0);
@@ -177,7 +177,7 @@
   // public static String returnNull() { return null; }
   MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
   [](MethodBuilder& method) {
-    Value zero = method.MakeRegister();
+    LiveRegister zero = method.AllocRegister();
     method.BuildConst4(zero, 0);
     method.BuildReturn(zero, /*is_object=*/true);
   }(returnNull);
@@ -188,7 +188,7 @@
   // public static String makeString() { return "Hello, World!"; }
   MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
   [](MethodBuilder& method) {
-    Value string = method.MakeRegister();
+    LiveRegister string = method.AllocRegister();
     method.BuildConstString(string, "Hello, World!");
     method.BuildReturn(string, /*is_object=*/true);
   }(makeString);
@@ -200,7 +200,7 @@
   MethodBuilder returnStringIfZeroAB{
       cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
   [&](MethodBuilder& method) {
-    Value resultIfZero{method.MakeRegister()};
+    LiveRegister resultIfZero{method.AllocRegister()};
     Value else_target{method.MakeLabel()};
     method.AddInstruction(Instruction::OpWithArgs(
         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
@@ -220,7 +220,7 @@
   MethodBuilder returnStringIfZeroBA{
       cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
   [&](MethodBuilder& method) {
-    Value resultIfZero{method.MakeRegister()};
+    LiveRegister resultIfZero{method.AllocRegister()};
     Value else_target{method.MakeLabel()};
     method.AddInstruction(Instruction::OpWithArgs(
         Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
@@ -244,7 +244,7 @@
       cbuilder.CreateMethod("invokeStaticReturnObject",
                             Prototype{string_type, TypeDescriptor::Int(), TypeDescriptor::Int()})};
   [&](MethodBuilder& method) {
-    Value result{method.MakeRegister()};
+    LiveRegister result{method.AllocRegister()};
     MethodDeclData to_string{dex_file.GetOrDeclareMethod(
         TypeDescriptor::FromClassname("java.lang.Integer"),
         "toString",
@@ -260,7 +260,7 @@
   MethodBuilder invokeVirtualReturnObject{cbuilder.CreateMethod(
       "invokeVirtualReturnObject", Prototype{string_type, string_type, TypeDescriptor::Int()})};
   [&](MethodBuilder& method) {
-    Value result{method.MakeRegister()};
+    LiveRegister result{method.AllocRegister()};
     MethodDeclData substring{dex_file.GetOrDeclareMethod(
         string_type, "substring", Prototype{string_type, TypeDescriptor::Int()})};
     method.AddInstruction(Instruction::InvokeVirtualObject(
@@ -291,7 +291,7 @@
   [&](MethodBuilder& method) {
     const ir::FieldDecl* field =
         dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
-    Value result{method.MakeRegister()};
+    LiveRegister result{method.AllocRegister()};
     method.AddInstruction(Instruction::GetStaticField(field->orig_index, result));
     method.BuildReturn(result, /*is_object=*/false);
     method.Encode();
@@ -304,7 +304,7 @@
   [&](MethodBuilder& method) {
     const ir::FieldDecl* field =
         dex_file.GetOrAddField(test_class, "staticInteger", TypeDescriptor::Int());
-    Value number{method.MakeRegister()};
+    LiveRegister number{method.AllocRegister()};
     method.BuildConst4(number, 7);
     method.AddInstruction(Instruction::SetStaticField(field->orig_index, number));
     method.BuildReturn();
@@ -318,7 +318,7 @@
   [&](MethodBuilder& method) {
     const ir::FieldDecl* field =
         dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
-    Value result{method.MakeRegister()};
+    LiveRegister result{method.AllocRegister()};
     method.AddInstruction(Instruction::GetField(field->orig_index, result, Value::Parameter(0)));
     method.BuildReturn(result, /*is_object=*/false);
     method.Encode();
@@ -331,7 +331,7 @@
   [&](MethodBuilder& method) {
     const ir::FieldDecl* field =
         dex_file.GetOrAddField(test_class, "instanceField", TypeDescriptor::Int());
-    Value number{method.MakeRegister()};
+    LiveRegister number{method.AllocRegister()};
     method.BuildConst4(number, 7);
     method.AddInstruction(Instruction::SetField(field->orig_index, Value::Parameter(0), number));
     method.BuildReturn();
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/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 2aca206..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,14 @@
             "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
      */
@@ -3242,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);
@@ -3572,6 +3594,7 @@
         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());
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/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 6a3c06e..1ffed25 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -357,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
@@ -839,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)}.
@@ -1146,7 +1191,6 @@
                             () -> psl.onPhysicalChannelConfigurationChanged(configs)));
         }
 
-        @Override
         public void onEmergencyNumberListChanged(Map emergencyNumberList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
@@ -1156,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/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 58f2858..f527bc3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -578,7 +578,8 @@
         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) {
             Log.d("SubscriptionInfo", "canManageSubscription: Unknown package: " + packageName, e);
             return false;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d3cba2e..dcd35fd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -896,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;
 
@@ -2123,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);
     }
 
@@ -2701,7 +2707,8 @@
         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) {
             logd("Unknown package: " + packageName);
             return false;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f25b012..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 "";
         }
@@ -3251,6 +3268,31 @@
         }
     }
 
+
+    /**
+     * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+     * on the UICC card.
+     *
+     * 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.
      *
@@ -4516,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
      */
@@ -5042,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).
@@ -6600,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();
     }
 
     /**
@@ -8630,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) {
@@ -8643,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,
@@ -8763,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) {
@@ -8799,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
@@ -8818,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
@@ -8833,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
@@ -8853,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 811722f..93ccba1 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -20,6 +20,7 @@
 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;
@@ -169,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;
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 77ee205..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,6 +570,29 @@
         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() {
@@ -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/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/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/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/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/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/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 28b331b..da32c8c 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
      */
-    public boolean isUsimDataDownload() {
+    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 34ac3dc..81b1e49 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -19,10 +19,9 @@
 java_sdk_library {
     name: "android.test.mock",
 
-    srcs: [
-        "src/**/*.java",
-        ":framework-srcs",
-    ],
+    srcs: ["src/**/*.java"],
+    api_srcs: [":framework-all-sources"],
+    libs: ["framework-all"],
 
     api_packages: [
         "android.test.mock",
diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
new file mode 100644
index 0000000..adcbb428
--- /dev/null
+++ b/tests/ApkVerityTest/Android.bp
@@ -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.
+
+java_test_host {
+    name: "ApkVerityTests",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"],
+    test_suites: ["general-tests"],
+    target_required: [
+        "block_device_writer_module",
+        "ApkVerityTestApp",
+        "ApkVerityTestAppSplit",
+    ],
+    data: [
+        ":ApkVerityTestCertDer",
+        ":ApkVerityTestAppFsvSig",
+        ":ApkVerityTestAppDm",
+        ":ApkVerityTestAppDmFsvSig",
+        ":ApkVerityTestAppSplitFsvSig",
+        ":ApkVerityTestAppSplitDm",
+        ":ApkVerityTestAppSplitDmFsvSig",
+    ],
+}
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
new file mode 100644
index 0000000..73779cb
--- /dev/null
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?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="APK fs-verity integration/regression test">
+    <option name="test-suite-tag" value="apct" />
+
+    <!-- This test requires root to write against block device. -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- Disable package verifier prevents it holding the target APK's fd that prevents cache
+             eviction. -->
+        <option name="set-global-setting" key="package_verifier_enable" value="0" />
+        <option name="restore-settings" value="true" />
+
+        <!-- Skip in order to prevent reboot that confuses the test flow. -->
+        <option name="force-skip-system-props" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+        <option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="ApkVerityTests.jar" />
+    </test>
+</configuration>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/Android.bp b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp
new file mode 100644
index 0000000..69632b2
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/Android.bp
@@ -0,0 +1,29 @@
+// 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_test_helper_app {
+  name: "ApkVerityTestApp",
+  manifest: "AndroidManifest.xml",
+  srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+  name: "ApkVerityTestAppSplit",
+  manifest: "feature_split/AndroidManifest.xml",
+  srcs: ["src/**/*.java"],
+  aaptflags: [
+      "--custom-package com.android.apkverity.feature_x",
+      "--package-id 0x80",
+  ],
+}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..0b3ff77
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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"
+    package="com.android.apkverity">
+    <application>
+        <activity android:name=".DummyActivity"/>
+    </application>
+</manifest>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.xml
new file mode 100644
index 0000000..3f1a4f3
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/AndroidManifest.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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.apkverity"
+    android:isFeatureSplit="true"
+    split="feature_x">
+    <application>
+        <activity android:name=".feature_x.DummyActivity"/>
+    </application>
+</manifest>
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
new file mode 100644
index 0000000..0f694c2
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/feature_split/src/com/android/apkverity/feature_x/DummyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.apkverity.feature_x;
+
+import android.app.Activity;
+
+/** Dummy class just to generate some dex */
+public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
new file mode 100644
index 0000000..837c7be
--- /dev/null
+++ b/tests/ApkVerityTest/ApkVerityTestApp/src/com/android/apkverity/DummyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.apkverity;
+
+import android.app.Activity;
+
+/** Dummy class just to generate some dex */
+public class DummyActivity extends Activity {}
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
new file mode 100644
index 0000000..deed3a0
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -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.
+
+// This is a cc_test just because it supports test_suites. This should be converted to something
+// like cc_binary_test_helper once supported.
+cc_test {
+    // Depending on how the test runs, the executable may be uploaded to different location.
+    // Before the bug in the file pusher is fixed, workaround by making the name unique.
+    // See b/124718249#comment12.
+    name: "block_device_writer_module",
+    stem: "block_device_writer",
+
+    srcs: ["block_device_writer.cpp"],
+    cflags: ["-Wall", "-Werror", "-Wextra", "-g"],
+    shared_libs: ["libbase", "libutils"],
+
+    test_suites: ["general-tests"],
+    gtest: false,
+}
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
new file mode 100644
index 0000000..b0c7251
--- /dev/null
+++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
@@ -0,0 +1,189 @@
+/*
+ * 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 <cassert>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <memory>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fiemap.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+
+// This program modifies a file at given offset, but directly against the block
+// device, purposely to bypass the filesystem. Note that the change on block
+// device may not reflect the same way when read from filesystem, for example,
+// when the file is encrypted on disk.
+//
+// Only one byte is supported for now just so that we don't need to handle the
+// case when the range crosses different "extents".
+//
+// References:
+//  https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
+//  https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
+
+ssize_t get_logical_block_size(const char* block_device) {
+  android::base::unique_fd fd(open(block_device, O_RDONLY));
+  if (fd.get() < 0) {
+    fprintf(stderr, "open %s failed\n", block_device);
+    return -1;
+  }
+
+  int size;
+  if (ioctl(fd, BLKSSZGET, &size) < 0) {
+    fprintf(stderr, "ioctl(BLKSSZGET) failed: %s\n", strerror(errno));
+    return -1;
+  }
+  return size;
+}
+
+int64_t get_physical_offset(const char* file_name, uint64_t byte_offset) {
+  android::base::unique_fd fd(open(file_name, O_RDONLY));
+  if (fd.get() < 0) {
+    fprintf(stderr, "open %s failed\n", file_name);
+    return -1;
+  }
+
+  const int map_size = sizeof(struct fiemap) + sizeof(struct fiemap_extent);
+  char fiemap_buffer[map_size] = {0};
+  struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(&fiemap_buffer);
+
+  fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+  fiemap->fm_start = byte_offset;
+  fiemap->fm_length = 1;
+  fiemap->fm_extent_count = 1;
+
+  int ret = ioctl(fd.get(), FS_IOC_FIEMAP, fiemap);
+  if (ret < 0) {
+    fprintf(stderr, "ioctl(FS_IOC_FIEMAP) failed: %s\n", strerror(errno));
+    return -1;
+  }
+
+  if (fiemap->fm_mapped_extents != 1) {
+    fprintf(stderr, "fm_mapped_extents != 1 (is %d)\n",
+            fiemap->fm_mapped_extents);
+    return -1;
+  }
+
+  struct fiemap_extent* extent = &fiemap->fm_extents[0];
+  printf(
+      "logical offset: %llu, physical offset: %llu, length: %llu, "
+      "flags: %x\n",
+      extent->fe_logical, extent->fe_physical, extent->fe_length,
+      extent->fe_flags);
+  if (extent->fe_flags & (FIEMAP_EXTENT_UNKNOWN |
+                          FIEMAP_EXTENT_UNWRITTEN)) {
+    fprintf(stderr, "Failed to locate physical offset safely\n");
+    return -1;
+  }
+
+  return extent->fe_physical + (byte_offset - extent->fe_logical);
+}
+
+int read_block_from_device(const char* device_path, uint64_t block_offset,
+                           ssize_t block_size, char* block_buffer) {
+  assert(block_offset % block_size == 0);
+  android::base::unique_fd fd(open(device_path, O_RDONLY | O_DIRECT));
+  if (fd.get() < 0) {
+    fprintf(stderr, "open %s failed\n", device_path);
+    return -1;
+  }
+
+  ssize_t retval =
+      TEMP_FAILURE_RETRY(pread(fd, block_buffer, block_size, block_offset));
+  if (retval != block_size) {
+    fprintf(stderr, "read returns error or incomplete result (%zu): %s\n",
+            retval, strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+int write_block_to_device(const char* device_path, uint64_t block_offset,
+                          ssize_t block_size, char* block_buffer) {
+  assert(block_offset % block_size == 0);
+  android::base::unique_fd fd(open(device_path, O_WRONLY | O_DIRECT));
+  if (fd.get() < 0) {
+    fprintf(stderr, "open %s failed\n", device_path);
+    return -1;
+  }
+
+  ssize_t retval = TEMP_FAILURE_RETRY(
+      pwrite(fd.get(), block_buffer, block_size, block_offset));
+  if (retval != block_size) {
+    fprintf(stderr, "write returns error or incomplete result (%zu): %s\n",
+            retval, strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+int main(int argc, const char** argv) {
+  if (argc != 4) {
+    fprintf(stderr,
+            "Usage: %s block_dev filename byte_offset\n"
+            "\n"
+            "This program bypasses filesystem and damages the specified byte\n"
+            "at the physical position on <block_dev> corresponding to the\n"
+            "logical byte location in <filename>.\n",
+            argv[0]);
+    return -1;
+  }
+
+  const char* block_device = argv[1];
+  const char* file_name = argv[2];
+  uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
+
+  ssize_t block_size = get_logical_block_size(block_device);
+  if (block_size < 0) {
+    return -1;
+  }
+
+  int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
+  if (physical_offset_signed < 0) {
+    return -1;
+  }
+
+  uint64_t physical_offset = static_cast<uint64_t>(physical_offset_signed);
+  uint64_t offset_within_block = physical_offset % block_size;
+  uint64_t physical_block_offset = physical_offset - offset_within_block;
+
+  // Direct I/O requires aligned buffer
+  std::unique_ptr<char> buf(static_cast<char*>(
+      aligned_alloc(block_size /* alignment */, block_size /* size */)));
+
+  if (read_block_from_device(block_device, physical_block_offset, block_size,
+                             buf.get()) < 0) {
+    return -1;
+  }
+  char* p = buf.get() + offset_within_block;
+  printf("before: %hhx\n", *p);
+  *p ^= 0xff;
+  printf("after: %hhx\n", *p);
+  if (write_block_to_device(block_device, physical_block_offset, block_size,
+                            buf.get()) < 0) {
+    return -1;
+  }
+
+  return 0;
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
new file mode 100644
index 0000000..761c5ce
--- /dev/null
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -0,0 +1,496 @@
+/*
+ * 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.apkverity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * This test makes sure app installs with fs-verity signature, and on-access verification works.
+ *
+ * <p>When an app is installed, all or none of the files should have their corresponding .fsv_sig
+ * signature file. Otherwise, install will fail.
+ *
+ * <p>Once installed, file protected by fs-verity is verified by kernel every time a block is loaded
+ * from disk to memory. The file is immutable by design, enforced by filesystem.
+ *
+ * <p>In order to make sure a block of the file is readable only if the underlying block on disk
+ * stay intact, the test needs to bypass the filesystem and tampers with the corresponding physical
+ * address against the block device.
+ *
+ * <p>Requirements to run this test:
+ * <ul>
+ *   <li>Device is rootable</li>
+ *   <li>The filesystem supports fs-verity</li>
+ *   <li>The feature flag is enabled</li>
+ * </ul>
+ */
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApkVerityTest extends BaseHostJUnit4Test {
+    private static final String TARGET_PACKAGE = "com.android.apkverity";
+
+    private static final String BASE_APK = "ApkVerityTestApp.apk";
+    private static final String BASE_APK_DM = "ApkVerityTestApp.dm";
+    private static final String SPLIT_APK = "ApkVerityTestAppSplit.apk";
+    private static final String SPLIT_APK_DM = "ApkVerityTestAppSplit.dm";
+
+    private static final String INSTALLED_BASE_APK = "base.apk";
+    private static final String INSTALLED_BASE_DM = "base.dm";
+    private static final String INSTALLED_SPLIT_APK = "split_feature_x.apk";
+    private static final String INSTALLED_SPLIT_DM = "split_feature_x.dm";
+    private static final String INSTALLED_BASE_APK_FSV_SIG = "base.apk.fsv_sig";
+    private static final String INSTALLED_BASE_DM_FSV_SIG = "base.dm.fsv_sig";
+    private static final String INSTALLED_SPLIT_APK_FSV_SIG = "split_feature_x.apk.fsv_sig";
+    private static final String INSTALLED_SPLIT_DM_FSV_SIG = "split_feature_x.dm.fsv_sig";
+
+    private static final String DAMAGING_EXECUTABLE = "/data/local/tmp/block_device_writer";
+    private static final String CERT_PATH = "/data/local/tmp/ApkVerityTestCert.der";
+
+    private static final String APK_VERITY_STANDARD_MODE = "2";
+
+    /** Only 4K page is supported by fs-verity currently. */
+    private static final int FSVERITY_PAGE_SIZE = 4096;
+
+    private ITestDevice mDevice;
+    private String mKeyId;
+
+    @Before
+    public void setUp() throws DeviceNotAvailableException {
+        mDevice = getDevice();
+
+        String apkVerityMode = mDevice.getProperty("ro.apk_verity.mode");
+        assumeTrue(APK_VERITY_STANDARD_MODE.equals(apkVerityMode));
+
+        mKeyId = expectRemoteCommandToSucceed(
+                "mini-keyctl padd asymmetric fsv_test .fs-verity < " + CERT_PATH).trim();
+        if (!mKeyId.matches("^\\d+$")) {
+            String keyId = mKeyId;
+            mKeyId = null;
+            fail("Key ID is not decimal: " + keyId);
+        }
+
+        uninstallPackage(TARGET_PACKAGE);
+    }
+
+    @After
+    public void tearDown() throws DeviceNotAvailableException {
+        uninstallPackage(TARGET_PACKAGE);
+
+        if (mKeyId != null) {
+            expectRemoteCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
+        }
+    }
+
+    @Test
+    public void testFsverityKernelSupports() throws DeviceNotAvailableException {
+        ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
+        expectRemoteCommandToSucceed("test -f /sys/fs/" + mountPoint.type + "/features/verity");
+    }
+
+    @Test
+    public void testInstallBase() throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG);
+        verifyInstalledFilesHaveFsverity();
+    }
+
+    @Test
+    public void testInstallBaseWithWrongSignature()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFile(SPLIT_APK_DM + ".fsv_sig",
+                        BASE_APK + ".fsv_sig")
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallBaseWithSplit()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .addFileAndSignature(SPLIT_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_APK_FSV_SIG);
+        verifyInstalledFilesHaveFsverity();
+    }
+
+    @Test
+    public void testInstallBaseWithDm() throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .addFileAndSignature(BASE_APK_DM)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG,
+                INSTALLED_BASE_DM,
+                INSTALLED_BASE_DM_FSV_SIG);
+        verifyInstalledFilesHaveFsverity();
+    }
+
+    @Test
+    public void testInstallEverything() throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .addFileAndSignature(BASE_APK_DM)
+                .addFileAndSignature(SPLIT_APK)
+                .addFileAndSignature(SPLIT_APK_DM)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG,
+                INSTALLED_BASE_DM,
+                INSTALLED_BASE_DM_FSV_SIG,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_APK_FSV_SIG,
+                INSTALLED_SPLIT_DM,
+                INSTALLED_SPLIT_DM_FSV_SIG);
+        verifyInstalledFilesHaveFsverity();
+    }
+
+    @Test
+    public void testInstallSplitOnly()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG);
+
+        new InstallMultiple()
+                .inheritFrom(TARGET_PACKAGE)
+                .addFileAndSignature(SPLIT_APK)
+                .run();
+
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_APK_FSV_SIG);
+        verifyInstalledFilesHaveFsverity();
+    }
+
+    @Test
+    public void testInstallSplitOnlyMissingSignature()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG);
+
+        new InstallMultiple()
+                .inheritFrom(TARGET_PACKAGE)
+                .addFile(SPLIT_APK)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallSplitOnlyWithoutBaseSignature()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFile(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        verifyInstalledFiles(INSTALLED_BASE_APK);
+
+        new InstallMultiple()
+                .inheritFrom(TARGET_PACKAGE)
+                .addFileAndSignature(SPLIT_APK)
+                .run();
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_SPLIT_APK,
+                INSTALLED_SPLIT_APK_FSV_SIG);
+
+    }
+
+    @Test
+    public void testInstallOnlyBaseHasFsvSig()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .addFile(BASE_APK_DM)
+                .addFile(SPLIT_APK)
+                .addFile(SPLIT_APK_DM)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallOnlyDmHasFsvSig()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFileAndSignature(BASE_APK_DM)
+                .addFile(SPLIT_APK)
+                .addFile(SPLIT_APK_DM)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallOnlySplitHasFsvSig()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFile(BASE_APK)
+                .addFile(BASE_APK_DM)
+                .addFileAndSignature(SPLIT_APK)
+                .addFile(SPLIT_APK_DM)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallBaseWithFsvSigThenSplitWithout()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFileAndSignature(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        verifyInstalledFiles(
+                INSTALLED_BASE_APK,
+                INSTALLED_BASE_APK_FSV_SIG);
+
+        new InstallMultiple()
+                .addFile(SPLIT_APK)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testInstallBaseWithoutFsvSigThenSplitWith()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple()
+                .addFile(BASE_APK)
+                .run();
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        verifyInstalledFiles(INSTALLED_BASE_APK);
+
+        new InstallMultiple()
+                .addFileAndSignature(SPLIT_APK)
+                .runExpectingFailure();
+    }
+
+    @Test
+    public void testFsverityFileIsImmutableAndReadable() throws DeviceNotAvailableException {
+        new InstallMultiple().addFileAndSignature(BASE_APK).run();
+        String apkPath = getApkPath(TARGET_PACKAGE);
+
+        assertNotNull(getDevice().getAppPackageInfo(TARGET_PACKAGE));
+        expectRemoteCommandToFail("echo -n '' >> " + apkPath);
+        expectRemoteCommandToSucceed("cat " + apkPath + " > /dev/null");
+    }
+
+    @Test
+    public void testFsverityFailToReadModifiedBlockAtFront() throws DeviceNotAvailableException {
+        new InstallMultiple().addFileAndSignature(BASE_APK).run();
+        String apkPath = getApkPath(TARGET_PACKAGE);
+
+        long apkSize = getFileSizeInBytes(apkPath);
+        long offsetFirstByte = 0;
+
+        // The first two pages should be both readable at first.
+        assertTrue(canReadByte(apkPath, offsetFirstByte));
+        if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
+            assertTrue(canReadByte(apkPath, offsetFirstByte + FSVERITY_PAGE_SIZE));
+        }
+
+        // Damage the file directly against the block device.
+        damageFileAgainstBlockDevice(apkPath, offsetFirstByte);
+
+        // Expect actual read from disk to fail but only at damaged page.
+        dropCaches();
+        assertFalse(canReadByte(apkPath, offsetFirstByte));
+        if (apkSize > offsetFirstByte + FSVERITY_PAGE_SIZE) {
+            long lastByteOfTheSamePage =
+                    offsetFirstByte % FSVERITY_PAGE_SIZE + FSVERITY_PAGE_SIZE - 1;
+            assertFalse(canReadByte(apkPath, lastByteOfTheSamePage));
+            assertTrue(canReadByte(apkPath, lastByteOfTheSamePage + 1));
+        }
+    }
+
+    @Test
+    public void testFsverityFailToReadModifiedBlockAtBack() throws DeviceNotAvailableException {
+        new InstallMultiple().addFileAndSignature(BASE_APK).run();
+        String apkPath = getApkPath(TARGET_PACKAGE);
+
+        long apkSize = getFileSizeInBytes(apkPath);
+        long offsetOfLastByte = apkSize - 1;
+
+        // The first two pages should be both readable at first.
+        assertTrue(canReadByte(apkPath, offsetOfLastByte));
+        if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
+            assertTrue(canReadByte(apkPath, offsetOfLastByte - FSVERITY_PAGE_SIZE));
+        }
+
+        // Damage the file directly against the block device.
+        damageFileAgainstBlockDevice(apkPath, offsetOfLastByte);
+
+        // Expect actual read from disk to fail but only at damaged page.
+        dropCaches();
+        assertFalse(canReadByte(apkPath, offsetOfLastByte));
+        if (offsetOfLastByte - FSVERITY_PAGE_SIZE > 0) {
+            long firstByteOfTheSamePage = offsetOfLastByte - offsetOfLastByte % FSVERITY_PAGE_SIZE;
+            assertFalse(canReadByte(apkPath, firstByteOfTheSamePage));
+            assertTrue(canReadByte(apkPath, firstByteOfTheSamePage - 1));
+        }
+    }
+
+    private void verifyInstalledFilesHaveFsverity() throws DeviceNotAvailableException {
+        // Verify that all files are protected by fs-verity
+        String apkPath = getApkPath(TARGET_PACKAGE);
+        String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+        long kTargetOffset = 0;
+        for (String basename : expectRemoteCommandToSucceed("ls " + appDir).split("\n")) {
+            if (basename.endsWith(".apk") || basename.endsWith(".dm")) {
+                String path = appDir + "/" + basename;
+                damageFileAgainstBlockDevice(path, kTargetOffset);
+
+                // Retry is sometimes needed to pass the test. Package manager may have FD leaks
+                // (see b/122744005 as example) that prevents the file in question to be evicted
+                // from filesystem cache. Forcing GC workarounds the problem.
+                int retry = 5;
+                for (; retry > 0; retry--) {
+                    dropCaches();
+                    if (!canReadByte(path, kTargetOffset)) {
+                        break;
+                    }
+                    try {
+                        Thread.sleep(1000);
+                        String pid = expectRemoteCommandToSucceed("pidof system_server");
+                        mDevice.executeShellV2Command("kill -10 " + pid);  // force GC
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                        return;
+                    }
+                }
+                assertTrue("Read from " + path + " should fail", retry > 0);
+            }
+        }
+    }
+
+    private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException {
+        String apkPath = getApkPath(TARGET_PACKAGE);
+        String appDir = apkPath.substring(0, apkPath.lastIndexOf("/"));
+        HashSet<String> actualFiles = new HashSet<>(Arrays.asList(
+                expectRemoteCommandToSucceed("ls " + appDir).split("\n")));
+        assertTrue(actualFiles.remove("lib"));
+        assertTrue(actualFiles.remove("oat"));
+
+        HashSet<String> expectedFiles = new HashSet<>(Arrays.asList(filenames));
+        assertEquals(expectedFiles, actualFiles);
+    }
+
+    private void damageFileAgainstBlockDevice(String path, long offsetOfTargetingByte)
+            throws DeviceNotAvailableException {
+        assertTrue(path.startsWith("/data/"));
+        ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
+        expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
+                    mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
+    }
+
+    private String getApkPath(String packageName) throws DeviceNotAvailableException {
+        String line = expectRemoteCommandToSucceed("pm path " + packageName + " | grep base.apk");
+        int index = line.trim().indexOf(":");
+        assertTrue(index >= 0);
+        return line.substring(index + 1);
+    }
+
+    private long getFileSizeInBytes(String packageName) throws DeviceNotAvailableException {
+        return Long.parseLong(expectRemoteCommandToSucceed("stat -c '%s' " + packageName).trim());
+    }
+
+    private void dropCaches() throws DeviceNotAvailableException {
+        expectRemoteCommandToSucceed("sync && echo 1 > /proc/sys/vm/drop_caches");
+    }
+
+    private boolean canReadByte(String filePath, long offset) throws DeviceNotAvailableException {
+        CommandResult result = mDevice.executeShellV2Command(
+                "dd if=" + filePath + " bs=1 count=1 skip=" + Long.toString(offset));
+        return result.getStatus() == CommandStatus.SUCCESS;
+    }
+
+    private String expectRemoteCommandToSucceed(String cmd) throws DeviceNotAvailableException {
+        CommandResult result = mDevice.executeShellV2Command(cmd);
+        assertEquals("`" + cmd + "` failed: " + result.getStderr(), CommandStatus.SUCCESS,
+                result.getStatus());
+        return result.getStdout();
+    }
+
+    private void expectRemoteCommandToFail(String cmd) throws DeviceNotAvailableException {
+        CommandResult result = mDevice.executeShellV2Command(cmd);
+        assertTrue("Unexpected success from `" + cmd + "`: " + result.getStderr(),
+                result.getStatus() != CommandStatus.SUCCESS);
+    }
+
+    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+        InstallMultiple() {
+            super(getDevice(), getBuild());
+        }
+
+        InstallMultiple addFileAndSignature(String filename) {
+            try {
+                addFile(filename);
+                addFile(filename + ".fsv_sig");
+            } catch (FileNotFoundException e) {
+                fail("Missing test file: " + e);
+            }
+            return this;
+        }
+    }
+}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
new file mode 100644
index 0000000..02e73d1
--- /dev/null
+++ b/tests/ApkVerityTest/src/com/android/apkverity/BaseInstallMultiple.java
@@ -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.apkverity;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
+ *
+ * <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
+ * InstallMultiple() { super(getDevice(), null); } } </code>
+ */
+/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+
+    private final ITestDevice mDevice;
+    private final IBuildInfo mBuild;
+
+    private final List<String> mArgs = new ArrayList<>();
+    private final Map<File, String> mFileToRemoteMap = new HashMap<>();
+
+    /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
+        mDevice = device;
+        mBuild = buildInfo;
+        addArg("-g");
+    }
+
+    T addArg(String arg) {
+        mArgs.add(arg);
+        return (T) this;
+    }
+
+    T addFile(String filename) throws FileNotFoundException {
+        return addFile(filename, filename);
+    }
+
+    T addFile(String filename, String remoteName) throws FileNotFoundException {
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
+        mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
+        return (T) this;
+    }
+
+    T inheritFrom(String packageName) {
+        addArg("-r");
+        addArg("-p " + packageName);
+        return (T) this;
+    }
+
+    void run() throws DeviceNotAvailableException {
+        run(true);
+    }
+
+    void runExpectingFailure() throws DeviceNotAvailableException {
+        run(false);
+    }
+
+    private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
+        final ITestDevice device = mDevice;
+
+        // Create an install session
+        final StringBuilder cmd = new StringBuilder();
+        cmd.append("pm install-create");
+        for (String arg : mArgs) {
+            cmd.append(' ').append(arg);
+        }
+
+        String result = device.executeShellCommand(cmd.toString());
+        TestCase.assertTrue(result, result.startsWith("Success"));
+
+        final int start = result.lastIndexOf("[");
+        final int end = result.lastIndexOf("]");
+        int sessionId = -1;
+        try {
+            if (start != -1 && end != -1 && start < end) {
+                sessionId = Integer.parseInt(result.substring(start + 1, end));
+            }
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException("Failed to parse install session: " + result);
+        }
+        if (sessionId == -1) {
+            throw new IllegalStateException("Failed to create install session: " + result);
+        }
+
+        // Push our files into session. Ideally we'd use stdin streaming,
+        // but ddmlib doesn't support it yet.
+        for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
+            final File file = entry.getKey();
+            final String remoteName  = entry.getValue();
+            final String remotePath = "/data/local/tmp/" + file.getName();
+            if (!device.pushFile(file, remotePath)) {
+                throw new IllegalStateException("Failed to push " + file);
+            }
+
+            cmd.setLength(0);
+            cmd.append("pm install-write");
+            cmd.append(' ').append(sessionId);
+            cmd.append(' ').append(remoteName);
+            cmd.append(' ').append(remotePath);
+
+            result = device.executeShellCommand(cmd.toString());
+            TestCase.assertTrue(result, result.startsWith("Success"));
+        }
+
+        // Everything staged; let's pull trigger
+        cmd.setLength(0);
+        cmd.append("pm install-commit");
+        cmd.append(' ').append(sessionId);
+
+        result = device.executeShellCommand(cmd.toString());
+        if (expectingSuccess) {
+            TestCase.assertTrue(result, result.contains("Success"));
+        } else {
+            TestCase.assertFalse(result, result.contains("Success"));
+        }
+    }
+}
diff --git a/tests/ApkVerityTest/testdata/Android.bp b/tests/ApkVerityTest/testdata/Android.bp
new file mode 100644
index 0000000..c10b0ce
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/Android.bp
@@ -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.
+
+filegroup {
+    name: "ApkVerityTestKeyPem",
+    srcs: ["ApkVerityTestKey.pem"],
+}
+
+filegroup {
+    name: "ApkVerityTestCertPem",
+    srcs: ["ApkVerityTestCert.pem"],
+}
+
+filegroup {
+    name: "ApkVerityTestCertDer",
+    srcs: ["ApkVerityTestCert.der"],
+}
+
+filegroup {
+    name: "ApkVerityTestAppDm",
+    srcs: ["ApkVerityTestApp.dm"],
+}
+
+filegroup {
+    name: "ApkVerityTestAppSplitDm",
+    srcs: ["ApkVerityTestAppSplit.dm"],
+}
+
+genrule_defaults {
+    name: "apk_verity_sig_gen_default",
+    tools: ["fsverity"],
+    tool_files: [":ApkVerityTestKeyPem", ":ApkVerityTestCertPem"],
+    cmd: "$(location fsverity) sign $(in) $(out) " +
+        "--key=$(location :ApkVerityTestKeyPem) " +
+        "--cert=$(location :ApkVerityTestCertPem) " +
+        "> /dev/null",
+}
+
+genrule {
+    name: "ApkVerityTestAppFsvSig",
+    defaults: ["apk_verity_sig_gen_default"],
+    srcs: [":ApkVerityTestApp"],
+    out: ["ApkVerityTestApp.apk.fsv_sig"],
+}
+
+genrule {
+    name: "ApkVerityTestAppDmFsvSig",
+    defaults: ["apk_verity_sig_gen_default"],
+    srcs: [":ApkVerityTestAppDm"],
+    out: ["ApkVerityTestApp.dm.fsv_sig"],
+}
+
+genrule {
+    name: "ApkVerityTestAppSplitFsvSig",
+    defaults: ["apk_verity_sig_gen_default"],
+    srcs: [":ApkVerityTestAppSplit"],
+    out: ["ApkVerityTestAppSplit.apk.fsv_sig"],
+}
+
+genrule {
+    name: "ApkVerityTestAppSplitDmFsvSig",
+    defaults: ["apk_verity_sig_gen_default"],
+    srcs: [":ApkVerityTestAppSplitDm"],
+    out: ["ApkVerityTestAppSplit.dm.fsv_sig"],
+}
+
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
new file mode 100644
index 0000000..e53a861
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestApp.dm
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
new file mode 100644
index 0000000..75396f1
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestAppSplit.dm
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.der b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der
new file mode 100644
index 0000000..fe9029b
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.der
Binary files differ
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
new file mode 100644
index 0000000..6c0b7b1
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestCert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFLjCCAxagAwIBAgIJAKZbtMlZZwtdMA0GCSqGSIb3DQEBCwUAMCwxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UECgwHQW5kcm9pZDAeFw0xODEyMTky
+MTA5MzVaFw0xOTAxMTgyMTA5MzVaMCwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
+QTEQMA4GA1UECgwHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBAKnrw4WiFgFBq6vXqcLc97iwvcYPZmeIjQqYRF+CHwXBXx8IyDlMfPrgyIYo
+ZLkosnUK/Exuypdu6UEtdqtYPknC6w9z4YkxqsKtyxyB1b13ptcTHh3bf2N8bqGr
+8gWWLxj0QjumCtFi7Z/TCwB5t3b3gtC+0jVfABSWrm5PNkgk7jIP+4KeYLDCDfiJ
+XH3uHu6OASiSHTOnrmLWSaSw0y6G4OFthHqQnMywasly0r6m+Mif+K0ZUV7hBRi/
+SfqcJ1HTCXTJMskEyV6Qx2sHF/VbK2gdUv56z6OVRNSs/FxPBiWVMuZZKh1FpBVI
+gbGxusf2Awwtc+Soxr4/P1YFcrwfA/ff9FK3Yg/Cd3ZMGbzUkbEMEkE5BW7Gbjmx
+wz3mYTiRfa2L/Bl4MiMqNi0tfORLkmg+V/EItzfhZ/HsXMOCBsnuj4KnFslmbamz
+t9opypj2JLGk+lXhZ5gFNFw8tYH1AnG1AIXe5u+6Fq2nQ1y/ncGUTR5Sw4de/Gee
+C0UgR+KiFEdKupMKbXgSKl+0QPz/i2eSpcDOKMwZ4WiNrkbccbCyr38so+j5DfWF
+IeZA9a/IlysA6G8yU2TfXBc65VCIEQRJOQdUOZFDO8OSoqGP+fbA6edpmovGw+TH
+sM/NkmpEXpQm7BVOI4oVjdf4pKPp0zaW2YcaA3xU2w6eF17pAgMBAAGjUzBRMB0G
+A1UdDgQWBBRGpHYy7yiLEYalGuF1va6zJKGD/zAfBgNVHSMEGDAWgBRGpHYy7yiL
+EYalGuF1va6zJKGD/zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IC
+AQAao6ZBM122F0pYb2QLahIyyGEr3LfSdBGID4068pVik4ncIefFz36Xf9AFxRQd
+KHmwRYNPHiLRIEGdtqplC5pZDeHz41txIArNIZKzDWOYtdcFyCz8umuj912BmsoM
+YUQhT6F1sX53SWcKxEP/aJ2kltSlPFX99e3Vx9eRkceV1oe2NM6ZG8hnYCfCAMeJ
+jRTpbqCGaAsEHFtIx6wt3zEtUXIVg4aYFQs/qjTjeP8ByIj0b4lZrceEoTeRimuj
++4aAI+jBxLkwaN3hseQHzRNpgPehIVV/0RU92yzOD/WN4YwE6rwjKEI1lihHNBDa
++DwGtGbHmIUzjW1qArig+mzUIhfYIJAxrx20ynPz/Q+C7+iXhTDAYQlxTle0pX8m
+yM2DUdPo97eLOzQ4JDHxtcN3ntTEJKKvrmzKvWuxy/yoLwS7MtLH6RETTHabH3Qd
+CP83X7z8zTyxgPxHdfHo9sgR/4C9RHGJx4OpBTQaiqfjSpDqJSIQdbrHGOQDgYwL
+KQyiQuhukmNgRCB6dJoZJ/MyaNuMsXV9QobsDHW1oSuCvPAihVoWHJxt8m4Ma0jJ
+EIbEPT2Umw1F/P+CeXnVQwhPvzQKHCa+6cC/YdjTqIKLmQV8X3HUBUIMhP2JGDic
+MnUipTm/RwWZVOjCJaFqk5sVq3L0Lyd0XVUWSK1a4IcrsA==
+-----END CERTIFICATE-----
diff --git a/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
new file mode 100644
index 0000000..f0746c1
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/ApkVerityTestKey.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCp68OFohYBQaur
+16nC3Pe4sL3GD2ZniI0KmERfgh8FwV8fCMg5THz64MiGKGS5KLJ1CvxMbsqXbulB
+LXarWD5JwusPc+GJMarCrcscgdW9d6bXEx4d239jfG6hq/IFli8Y9EI7pgrRYu2f
+0wsAebd294LQvtI1XwAUlq5uTzZIJO4yD/uCnmCwwg34iVx97h7ujgEokh0zp65i
+1kmksNMuhuDhbYR6kJzMsGrJctK+pvjIn/itGVFe4QUYv0n6nCdR0wl0yTLJBMle
+kMdrBxf1WytoHVL+es+jlUTUrPxcTwYllTLmWSodRaQVSIGxsbrH9gMMLXPkqMa+
+Pz9WBXK8HwP33/RSt2IPwnd2TBm81JGxDBJBOQVuxm45scM95mE4kX2ti/wZeDIj
+KjYtLXzkS5JoPlfxCLc34Wfx7FzDggbJ7o+CpxbJZm2ps7faKcqY9iSxpPpV4WeY
+BTRcPLWB9QJxtQCF3ubvuhatp0Ncv53BlE0eUsOHXvxnngtFIEfiohRHSrqTCm14
+EipftED8/4tnkqXAzijMGeFoja5G3HGwsq9/LKPo+Q31hSHmQPWvyJcrAOhvMlNk
+31wXOuVQiBEESTkHVDmRQzvDkqKhj/n2wOnnaZqLxsPkx7DPzZJqRF6UJuwVTiOK
+FY3X+KSj6dM2ltmHGgN8VNsOnhde6QIDAQABAoICAGT21tWnisWyXKwd2BwWKgeO
+1SRDcEiihZO/CBlr+rzzum55TGdngHedauj0RW0Ttn3/SgysZCp415ZHylRjeZdg
+f0VOSLu5TEqi86X7q6IJ35O6I1IAY4AcpqvfvE3/f/qm4FgLADCMRL+LqeTdbdr9
+lLguOj9GNIkHQ5v96zYQ44vRnVNugetlUuHT1KZq/+wlaqDNuRZBU0gdJeL6wnDJ
+6gNojKg7F0A0ry8F0B1Cn16uVxebjJMAx4N93hpQALkI2XyQNGHnOzO6eROqQl0i
+j/csPW1CUfBUOHLaWpUKy483SOhAINsFz0pqK84G2gIItqTcuRksA/N1J1AYqqQO
++/8IK5Mb9j0RaYYrBG83luGCWYauAsWg2Yol6fUGju8IY/zavOaES42XogY588Ad
+JzW+njjxXcnoD/u5keWrGwbPdGfoaLLg4eMlRBT4yNicyT04knXjFG4QTfLY5lF/
+VKdvZk6RMoCLdAtgN6EKHtcwuoYR967otsbavshngZ9HE/ic5/TdNFCBjxs6q9bm
+docC4CLHU/feXvOCYSnIfUpDzEPV96Gbk6o0qeYn3RUSGzRpXQHxXXfszEESUWnd
+2rtfXxqA7C5n8CshBfKJND7/LKRGpBRaYWJtc4hFmo8prhXfOb40PEZNlx8mcsEz
+WYZpmvFQHU8+bZIm0a5RAoIBAQDaCAje9xLKN1CYzygA/U3x2CsGuWWyh9xM1oR5
+5t+nn0EeIMrzGuHrD4hdbZiTiJcO5dpSg/3dssc/QLJEdv+BoMEgSYTf3TX03dIb
+kSlj+ONobejO4nVoUP0axTvVe/nuMYvLguTM6OCFvgV752TFxVyVHl6RM+rQYGCl
+ajbBCsCRg4QgpZ/RHWf+3KMJunzwWBlsAXcjOudneYqEl713h/q1lc5cONIglQDU
+E+bc5q6q++c/H8dYaWq4QE4CQU8wsq77/bZk8z1jheOV0HkwaH5ShtKD7bk/4MA9
+jWQUDW6/LRXkNiPExsAZxnP3mxhtUToWq1nedF6kPmNBko+9AoIBAQDHgvAql6i7
+osTYUcY/GldPmnrvfqbKmD0nI8mnaJfN2vGwjB/ol3lm+pviKIQ4ER80xsdn4xK0
+2cC9OdkY2UX7zilKohxvKVsbVOYpUwfpoRQO1Euddb6iAMqgGDyDzRBDTzTx5pB5
+XL9B/XuJVCMkjsNdD9iEbjdY1Epv7kYf53zfvrXdqv24uSNAszPFBLLPHSC9yONE
+a/t3mHGZ2cjr52leGNGY7ib6GNGBUeA34SM9g97tU9pAgy712RfZhH6fA93CLk6T
+DKoch56YId71vZt2J0Lrk4TWnnpidSoRmzKfVIJwjCmgYbI+2eDp7h0Z0DnDbji6
+9BPt3RWsoZidAoIBAA2A7+O3U7+Ye3JraiPdjGVNKSUKeIT9KyTLKHtQVEvSbjsK
+dudlo9ZmKOD4d7mzfP+cNtBjgmanuvVs8V2SLTL/HNb+Fq+yyLO4xVmVvQWHFbaT
+EBc4KWNjmLl+u7z2J72b7feVzMvwJG/EHBzXcQNavOgzcFH38DQls/aqxGdiXhjl
+F1raRzKxao57ZdGlbjWIj1KEKLfS3yAmg/DAYSi1EE8MzzIhBsqjz+BStzq5Qtou
+Ld1X/4W3SbfNq8cx+lCe0H2k8hYAhq3STg0qU0cvQZuk5Abtw0p0hhOJ3UfsqQ5I
+IZH31HFMiftOskIEphenLzzWMgO4G2B6yLT3+dUCggEAOLF1i7Ti5sbfBtVd70qN
+6vnr2yhzPvi5z+h0ghTPpliD+3YmDxMUFXY7W63FvKTo6DdgLJ4zD58dDOhmT5BW
+ObKguyuLxu7Ki965NJ76jaIPMBOVlR4DWMe+zHV2pMFd0LKuSdsJzOLVGmxscV6u
+SdIjo8s/7InhQmW47UuZM7G1I2NvDJltVdOON/F0UZT/NqmBR0zRf/zrTVXNWjmv
+xZFRuMJ2tO1fuAvbZNMeUuKv/+f8LhZ424IrkwLoqw/iZ09S8b306AZeRJMpNvPR
+BqWlipKnioe15MLN5jKDDNO8M9hw5Ih/v6pjW0bQicj3DgHEmEs25bE8BIihgxe8
+ZQKCAQEAsWKsUv13OEbYYAoJgbzDesWF9NzamFB0NLyno9SChvFPH/d8RmAuti7Y
+BQUoBswLK24DF/TKf1YocsZq8tu+pnv0Nx1wtK4K+J3A1BYDm7ElpO3Km+HPUBtf
+C9KGT5hotlMQVTpYSDG/QeWbfl4UnNZcbg8pmv38NwV1eDoVDfaVrRYJzQn75+Tf
+s/WUq1x5PElR/4pNIU2i6pJGd6FimhRweJu/INR36spWmbMRNX8fyXx+9EBqMbVp
+vS2xGgxxQT6bAvBfRlpgi87T9v5Gqoy6/jM/wX9smH9PfUV1vK32n3Zrbd46gwZW
+p2aUlQOLXU9SjQTirZbdCZP0XHtFsg==
+-----END PRIVATE KEY-----
diff --git a/tests/ApkVerityTest/testdata/README.md b/tests/ApkVerityTest/testdata/README.md
new file mode 100644
index 0000000..163cb18
--- /dev/null
+++ b/tests/ApkVerityTest/testdata/README.md
@@ -0,0 +1,13 @@
+This test only runs on rooted / debuggable device.
+
+The test tries to install subsets of base.{apk,dm}, split.{apk,dm} and their
+corresponding .fsv_sig files (generated by build rule). If installed, the
+tests also tries to tamper with the file at absolute disk offset to verify
+if fs-verity is effective.
+
+How to generate dex metadata (.dm)
+==================================
+
+  adb shell profman --generate-test-profile=/data/local/tmp/primary.prof
+  adb pull /data/local/tmp/primary.prof
+  zip foo.dm primary.prof
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
index 17986a3..730b210 100644
--- a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -66,10 +66,18 @@
         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.
+        // Wait up to 20 seconds for the profile to be saved.
+        for (int i = 0; i < 20; ++i) {
+            // Force save the profile since we truncated it.
+            forceSaveProfile("system_server");
+            String s = mTestDevice.executeShellCommand("wc -c <" + SYSTEM_SERVER_PROFILE).trim();
+            if (!"0".equals(s)) {
+                break;
+            }
+            Thread.sleep(1000);
+        }
+        // In case the profile is partially saved, wait an extra second.
         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);
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/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 5b1a36b..9b73abf 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <!-- Capture screen contents -->
     <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+    <!-- Enable / Disable tracing !-->
+    <uses-permission android:name="android.permission.DUMP" />
     <!-- Run layers trace -->
     <uses-permission android:name="android.permission.HARDWARE_TEST"/>
     <application>
@@ -33,4 +35,4 @@
                      android:targetPackage="com.android.server.wm.flicker"
                      android:label="WindowManager Flicker Tests">
     </instrumentation>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index e36f976..d433df5 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -25,5 +25,6 @@
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="directory-keys" value="/sdcard/flicker" />
         <option name="collect-on-run-ended-only" value="true" />
+        <option name="clean-up" value="false" />
     </metrics_collector>
 </configuration>
diff --git a/tests/FlickerTests/lib/Android.bp b/tests/FlickerTests/lib/Android.bp
deleted file mode 100644
index 5d8ed2c..0000000
--- a/tests/FlickerTests/lib/Android.bp
+++ /dev/null
@@ -1,44 +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.
-//
-
-java_test {
-    name: "flickerlib",
-    platform_apis: true,
-    srcs: ["src/**/*.java"],
-    static_libs: [
-        "androidx.test.janktesthelper",
-        "cts-wm-util",
-        "platformprotosnano",
-        "layersprotosnano",
-        "truth-prebuilt",
-        "sysui-helper",
-        "launcher-helper-lib",
-    ],
-}
-
-java_library {
-    name: "flickerautomationhelperlib",
-    sdk_version: "test_current",
-    srcs: [
-        "src/com/android/server/wm/flicker/AutomationUtils.java",
-        "src/com/android/server/wm/flicker/WindowUtils.java",
-    ],
-    static_libs: [
-        "sysui-helper",
-        "launcher-helper-lib",
-        "compatibility-device-util-axt",
-    ],
-}
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
deleted file mode 100644
index 84f9f871..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
+++ /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.
- */
-
-package com.android.server.wm.flicker;
-
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-/**
- * Collection of functional interfaces and classes representing assertions and their associated
- * 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 {
-    /**
-     * 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> {
-        /**
-         * Returns an assertion that represents the logical negation of this assertion.
-         *
-         * @return a assertion that represents the logical negation of this assertion
-         */
-        default TraceAssertion<T> negate() {
-            return (T t) -> apply(t).negate();
-        }
-    }
-
-    /**
-     * Checks assertion on a single layers trace entry.
-     */
-    @FunctionalInterface
-    interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
-
-    }
-
-    /**
-     * 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;
-
-        NamedAssertion(TraceAssertion<T> assertion, String name) {
-            this.assertion = assertion;
-            this.name = name;
-        }
-    }
-
-    /**
-     * 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;
-
-        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) {
-            this.success = success;
-            this.reason = reason;
-            this.assertionName = "";
-            this.timestamp = 0;
-        }
-
-        /**
-         * Returns the negated {@code Result} and adds a negation prefix to the assertion name.
-         */
-        Result negate() {
-            String negatedAssertionName;
-            if (this.assertionName.startsWith(NEGATION_PREFIX)) {
-                negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1);
-            } else {
-                negatedAssertionName = NEGATION_PREFIX + this.assertionName;
-            }
-            return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason);
-        }
-
-        boolean passed() {
-            return this.success;
-        }
-
-        boolean failed() {
-            return !this.success;
-        }
-
-        @Override
-        public String toString() {
-            return "Timestamp: " + prettyTimestamp(timestamp)
-                    + "\nAssertion: " + assertionName
-                    + "\nReason:   " + reason;
-        }
-
-        private String prettyTimestamp(long timestamp_ns) {
-            StringBuilder prettyTimestamp = new StringBuilder();
-            TimeUnit[] timeUnits = {TimeUnit.HOURS, TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit
-                    .MILLISECONDS};
-            String[] unitSuffixes = {"h", "m", "s", "ms"};
-
-            for (int i = 0; i < timeUnits.length; i++) {
-                long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS);
-                timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]);
-                prettyTimestamp.append(convertedTime).append(unitSuffixes[i]);
-            }
-
-            return prettyTimestamp.toString();
-        }
-    }
-}
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
deleted file mode 100644
index 3c65d3c..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
+++ /dev/null
@@ -1,183 +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 com.android.server.wm.flicker.Assertions.NamedAssertion;
-import com.android.server.wm.flicker.Assertions.Result;
-import com.android.server.wm.flicker.Assertions.TraceAssertion;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject}
- * used to filter trace entries and combine multiple assertions.
- *
- * @param <T> trace entry type
- */
-public class AssertionsChecker<T extends ITraceEntry> {
-    private boolean mFilterEntriesByRange = false;
-    private long mFilterStartTime = 0;
-    private long mFilterEndTime = 0;
-    private AssertionOption mOption = AssertionOption.NONE;
-    private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
-
-    void add(Assertions.TraceAssertion<T> assertion, String name) {
-        mAssertions.add(new NamedAssertion<>(assertion, name));
-    }
-
-    void filterByRange(long startTime, long endTime) {
-        mFilterEntriesByRange = true;
-        mFilterStartTime = startTime;
-        mFilterEndTime = endTime;
-    }
-
-    private void setOption(AssertionOption option) {
-        if (mOption != AssertionOption.NONE && option != mOption) {
-            throw new IllegalArgumentException("Cannot use " + mOption + " option with "
-                    + option + " option.");
-        }
-        mOption = option;
-    }
-
-    public void checkFirstEntry() {
-        setOption(AssertionOption.CHECK_FIRST_ENTRY);
-    }
-
-    public void checkLastEntry() {
-        setOption(AssertionOption.CHECK_LAST_ENTRY);
-    }
-
-    public void checkChangingAssertions() {
-        setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS);
-    }
-
-
-    /**
-     * Filters trace entries then runs assertions returning a list of failures.
-     *
-     * @param entries list of entries to perform assertions on
-     * @return list of failed assertion results
-     */
-    List<Result> test(List<T> entries) {
-        List<T> filteredEntries;
-        List<Result> failures;
-
-        if (mFilterEntriesByRange) {
-            filteredEntries = entries.stream()
-                    .filter(e -> ((e.getTimestamp() >= mFilterStartTime)
-                            && (e.getTimestamp() <= mFilterEndTime)))
-                    .collect(Collectors.toList());
-        } else {
-            filteredEntries = entries;
-        }
-
-        switch (mOption) {
-            case CHECK_CHANGING_ASSERTIONS:
-                return assertChanges(filteredEntries);
-            case CHECK_FIRST_ENTRY:
-                return assertEntry(filteredEntries.get(0));
-            case CHECK_LAST_ENTRY:
-                return assertEntry(filteredEntries.get(filteredEntries.size() - 1));
-        }
-        return assertAll(filteredEntries);
-    }
-
-    /**
-     * Steps through each trace entry checking if provided assertions are true in the order they
-     * are added. Each assertion must be true for at least a single trace entry.
-     *
-     * This can be used to check for asserting a change in property over a trace. Such as visibility
-     * for a window changes from true to false or top-most window changes from A to Bb and back to A
-     * again.
-     */
-    private List<Result> assertChanges(List<T> entries) {
-        List<Result> failures = new ArrayList<>();
-        int entryIndex = 0;
-        int assertionIndex = 0;
-        int lastPassedAssertionIndex = -1;
-
-        if (mAssertions.size() == 0) {
-            return failures;
-        }
-
-        while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) {
-            TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion;
-            Result result = currentAssertion.apply(entries.get(entryIndex));
-            if (result.passed()) {
-                lastPassedAssertionIndex = assertionIndex;
-                entryIndex++;
-                continue;
-            }
-
-            if (lastPassedAssertionIndex != assertionIndex) {
-                failures.add(result);
-                break;
-            }
-            assertionIndex++;
-
-            if (assertionIndex == mAssertions.size()) {
-                failures.add(result);
-                break;
-            }
-        }
-
-        if (failures.isEmpty()) {
-            if (assertionIndex != mAssertions.size() - 1) {
-                String reason = "\nAssertion " + mAssertions.get(assertionIndex).name
-                        + " never became false";
-                reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex)
-                        .map(assertion -> assertion.name).collect(Collectors.joining(","));
-                reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1)
-                        .map(assertion -> assertion.name).collect(Collectors.joining(","));
-
-                Result result = new Result(false /* success */, 0 /* timestamp */,
-                        "assertChanges", "Not all assertions passed." + reason);
-                failures.add(result);
-            }
-        }
-        return failures;
-    }
-
-    private List<Result> assertEntry(T entry) {
-        List<Result> failures = new ArrayList<>();
-        for (NamedAssertion<T> assertion : mAssertions) {
-            Result result = assertion.assertion.apply(entry);
-            if (result.failed()) {
-                failures.add(result);
-            }
-        }
-        return failures;
-    }
-
-    private List<Result> assertAll(List<T> entries) {
-        return mAssertions.stream().flatMap(
-                assertion -> entries.stream()
-                        .map(assertion.assertion)
-                        .filter(Result::failed))
-                .collect(Collectors.toList());
-    }
-
-    private enum AssertionOption {
-        NONE,
-        CHECK_CHANGING_ASSERTIONS,
-        CHECK_FIRST_ENTRY,
-        CHECK_LAST_ENTRY,
-    }
-}
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
deleted file mode 100644
index 9525f41..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
+++ /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 com.android.server.wm.flicker;
-
-/**
- * Common interface for Layer and WindowManager trace entries.
- */
-interface ITraceEntry {
-    /**
-     * @return timestamp of current entry
-     */
-    long getTimestamp();
-}
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
deleted file mode 100644
index 660ec0f..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
+++ /dev/null
@@ -1,412 +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 android.annotation.Nullable;
-import android.graphics.Rect;
-import android.surfaceflinger.nano.Layers.LayerProto;
-import android.surfaceflinger.nano.Layers.RectProto;
-import android.surfaceflinger.nano.Layers.RegionProto;
-import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
-import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
-import android.util.SparseArray;
-
-import com.android.server.wm.flicker.Assertions.Result;
-
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/**
- * Contains a collection of parsed Layers trace entries and assertions to apply over
- * a single entry.
- *
- * Each entry is parsed into a list of {@link LayersTrace.Entry} objects.
- */
-public class LayersTrace {
-    final private List<Entry> mEntries;
-    @Nullable
-    final private Path mSource;
-
-    private LayersTrace(List<Entry> entries, Path source) {
-        this.mEntries = entries;
-        this.mSource = source;
-    }
-
-    /**
-     * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
-     * of trace entries, storing the flattened layers into its hierarchical structure.
-     *
-     * @param data   binary proto data
-     * @param source Path to source of data for additional debug information
-     */
-    static LayersTrace parseFrom(byte[] data, Path source) {
-        List<Entry> entries = new ArrayList<>();
-        LayersTraceFileProto fileProto;
-        try {
-            fileProto = LayersTraceFileProto.parseFrom(data);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        for (LayersTraceProto traceProto : fileProto.entry) {
-            Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos,
-                    traceProto.layers.layers);
-            entries.add(entry);
-        }
-        return new LayersTrace(entries, source);
-    }
-
-    /**
-     * Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
-     * of trace entries, storing the flattened layers into its hierarchical structure.
-     *
-     * @param data binary proto data
-     */
-    static LayersTrace parseFrom(byte[] data) {
-        return parseFrom(data, null);
-    }
-
-    List<Entry> getEntries() {
-        return mEntries;
-    }
-
-    Entry getEntry(long timestamp) {
-        Optional<Entry> entry = mEntries.stream()
-                .filter(e -> e.getTimestamp() == timestamp)
-                .findFirst();
-        if (!entry.isPresent()) {
-            throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
-        }
-        return entry.get();
-    }
-
-    Optional<Path> getSource() {
-        return Optional.ofNullable(mSource);
-    }
-
-    /**
-     * Represents a single Layer trace entry.
-     */
-    static class Entry implements ITraceEntry {
-        private long mTimestamp;
-        private List<Layer> mRootLayers; // hierarchical representation of layers
-        private List<Layer> mFlattenedLayers = null;
-
-        private Entry(long timestamp, List<Layer> rootLayers) {
-            this.mTimestamp = timestamp;
-            this.mRootLayers = rootLayers;
-        }
-
-        /**
-         * Constructs the layer hierarchy from a flattened list of layers.
-         */
-        static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
-            SparseArray<Layer> layerMap = new SparseArray<>();
-            ArrayList<Layer> orphans = new ArrayList<>();
-            for (LayerProto proto : protos) {
-                int id = proto.id;
-                int parentId = proto.parent;
-
-                Layer newLayer = layerMap.get(id);
-                if (newLayer == null) {
-                    newLayer = new Layer(proto);
-                    layerMap.append(id, newLayer);
-                } else if (newLayer.mProto != null) {
-                    throw new RuntimeException("Duplicate layer id found:" + id);
-                } else {
-                    newLayer.mProto = proto;
-                    orphans.remove(newLayer);
-                }
-
-                // add parent placeholder
-                if (layerMap.get(parentId) == null) {
-                    Layer orphanLayer = new Layer(null);
-                    layerMap.append(parentId, orphanLayer);
-                    orphans.add(orphanLayer);
-                }
-                layerMap.get(parentId).addChild(newLayer);
-                newLayer.addParent(layerMap.get(parentId));
-            }
-
-            // Fail if we find orphan layers.
-            orphans.remove(layerMap.get(-1));
-            orphans.forEach(orphan -> {
-                String childNodes = orphan.mChildren.stream().map(node ->
-                        Integer.toString(node.getId())).collect(Collectors.joining(", "));
-                int orphanId = orphan.mChildren.get(0).mProto.parent;
-                throw new RuntimeException(
-                        "Failed to parse layers trace. Found orphan layers with parent "
-                                + "layer id:" + orphanId + " : " + childNodes);
-            });
-
-            return new Entry(timestamp, layerMap.get(-1).mChildren);
-        }
-
-        /**
-         * Extracts {@link Rect} from {@link RectProto}.
-         */
-        private static Rect extract(RectProto proto) {
-            return new Rect(proto.left, proto.top, proto.right, proto.bottom);
-        }
-
-        /**
-         * Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all
-         * the rects making up the region.
-         */
-        private static Rect extract(RegionProto regionProto) {
-            Rect region = new Rect();
-            for (RectProto proto : regionProto.rect) {
-                region.union(proto.left, proto.top, proto.right, proto.bottom);
-            }
-            return region;
-        }
-
-        /**
-         * Checks if a region specified by {@code testRect} is covered by all visible layers.
-         */
-        Result coversRegion(Rect testRect) {
-            String assertionName = "coversRegion";
-            Collection<Layer> layers = asFlattenedLayers();
-
-            for (int x = testRect.left; x < testRect.right; x++) {
-                for (int y = testRect.top; y < testRect.bottom; y++) {
-                    boolean emptyRegionFound = true;
-                    for (Layer layer : layers) {
-                        if (layer.isInvisible() || layer.isHiddenByParent()) {
-                            continue;
-                        }
-                        for (RectProto rectProto : layer.mProto.visibleRegion.rect) {
-                            Rect r = extract(rectProto);
-                            if (r.contains(x, y)) {
-                                y = r.bottom;
-                                emptyRegionFound = false;
-                            }
-                        }
-                    }
-                    if (emptyRegionFound) {
-                        String reason = "Region to test: " + testRect
-                                + "\nfirst empty point: " + x + ", " + y;
-                        reason += "\nvisible regions:";
-                        for (Layer layer : layers) {
-                            if (layer.isInvisible() || layer.isHiddenByParent()) {
-                                continue;
-                            }
-                            Rect r = extract(layer.mProto.visibleRegion);
-                            reason += "\n" + layer.mProto.name + r.toString();
-                        }
-                        return new Result(false /* success */, this.mTimestamp, assertionName,
-                                reason);
-                    }
-                }
-            }
-            String info = "Region covered: " + testRect;
-            return new Result(true /* success */, this.mTimestamp, assertionName, info);
-        }
-
-        /**
-         * Checks if a layer with name {@code layerName} has a visible region
-         * {@code expectedVisibleRegion}.
-         */
-        Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
-            String assertionName = "hasVisibleRegion";
-            String reason = "Could not find " + layerName;
-            for (Layer layer : asFlattenedLayers()) {
-                if (layer.mProto.name.contains(layerName)) {
-                    if (layer.isHiddenByParent()) {
-                        reason = layer.getHiddenByParentReason();
-                        continue;
-                    }
-                    if (layer.isInvisible()) {
-                        reason = layer.getVisibilityReason();
-                        continue;
-                    }
-                    Rect visibleRegion = extract(layer.mProto.visibleRegion);
-                    if (visibleRegion.equals(expectedVisibleRegion)) {
-                        return new Result(true /* success */, this.mTimestamp, assertionName,
-                                layer.mProto.name + "has visible region " + expectedVisibleRegion);
-                    }
-                    reason = layer.mProto.name + " has visible region:" + visibleRegion + " "
-                            + "expected:" + expectedVisibleRegion;
-                }
-            }
-            return new Result(false /* success */, this.mTimestamp, assertionName, reason);
-        }
-
-        /**
-         * Checks if a layer with name {@code layerName} is visible.
-         */
-        Result isVisible(String layerName) {
-            String assertionName = "isVisible";
-            String reason = "Could not find " + layerName;
-            for (Layer layer : asFlattenedLayers()) {
-                if (layer.mProto.name.contains(layerName)) {
-                    if (layer.isHiddenByParent()) {
-                        reason = layer.getHiddenByParentReason();
-                        continue;
-                    }
-                    if (layer.isInvisible()) {
-                        reason = layer.getVisibilityReason();
-                        continue;
-                    }
-                    return new Result(true /* success */, this.mTimestamp, assertionName,
-                            layer.mProto.name + " is visible");
-                }
-            }
-            return new Result(false /* success */, this.mTimestamp, assertionName, reason);
-        }
-
-        @Override
-        public long getTimestamp() {
-            return mTimestamp;
-        }
-
-        List<Layer> getRootLayers() {
-            return mRootLayers;
-        }
-
-        List<Layer> asFlattenedLayers() {
-            if (mFlattenedLayers == null) {
-                mFlattenedLayers = new ArrayList<>();
-                ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
-                while (!pendingLayers.isEmpty()) {
-                    Layer layer = pendingLayers.remove(0);
-                    mFlattenedLayers.add(layer);
-                    pendingLayers.addAll(layer.mChildren);
-                }
-            }
-            return mFlattenedLayers;
-        }
-
-        Rect getVisibleBounds(String layerName) {
-            List<Layer> layers = asFlattenedLayers();
-            for (Layer layer : layers) {
-                if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
-                    return extract(layer.mProto.visibleRegion);
-                }
-            }
-            return new Rect(0, 0, 0, 0);
-        }
-    }
-
-    /**
-     * Represents a single layer with links to its parent and child layers.
-     */
-    static class Layer {
-        @Nullable
-        LayerProto mProto;
-        List<Layer> mChildren;
-        @Nullable
-        Layer mParent = null;
-
-        private Layer(LayerProto proto) {
-            this.mProto = proto;
-            this.mChildren = new ArrayList<>();
-        }
-
-        private void addChild(Layer childLayer) {
-            this.mChildren.add(childLayer);
-        }
-
-        private void addParent(Layer parentLayer) {
-            this.mParent = parentLayer;
-        }
-
-        int getId() {
-            return mProto.id;
-        }
-
-        boolean isActiveBufferEmpty() {
-            return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
-                    || this.mProto.activeBuffer.width == 0;
-        }
-
-        boolean isVisibleRegionEmpty() {
-            if (this.mProto.visibleRegion == null) {
-                return true;
-            }
-            Rect visibleRect = Entry.extract(this.mProto.visibleRegion);
-            return visibleRect.height() == 0 || visibleRect.width() == 0;
-        }
-
-        boolean isHidden() {
-            return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
-        }
-
-        boolean isVisible() {
-            return (!isActiveBufferEmpty() || isColorLayer()) &&
-                    !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty();
-        }
-
-        boolean isColorLayer() {
-            return this.mProto.type.equals("ColorLayer");
-        }
-
-        boolean isRootLayer() {
-            return mParent == null || mParent.mProto == null;
-        }
-
-        boolean isInvisible() {
-            return !isVisible();
-        }
-
-        boolean isHiddenByParent() {
-            return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
-        }
-
-        String getHiddenByParentReason() {
-            String reason = "Layer " + mProto.name;
-            if (isHiddenByParent()) {
-                reason += " is hidden by parent: " + mParent.mProto.name;
-            } else {
-                reason += " is not hidden by parent: " + mParent.mProto.name;
-            }
-            return reason;
-        }
-
-        String getVisibilityReason() {
-            String reason = "Layer " + mProto.name;
-            if (isVisible()) {
-                reason += " is visible:";
-            } else {
-                reason += " is invisible:";
-                if (this.mProto.activeBuffer == null) {
-                    reason += " activeBuffer=null";
-                } else if (this.mProto.activeBuffer.height == 0) {
-                    reason += " activeBuffer.height=0";
-                } else if (this.mProto.activeBuffer.width == 0) {
-                    reason += " activeBuffer.width=0";
-                }
-                if (!isColorLayer()) {
-                    reason += " type != ColorLayer";
-                }
-                if (isHidden()) {
-                    reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
-                }
-                if (this.mProto.color.a == 0) {
-                    reason += " color.a=0";
-                }
-                if (isVisibleRegionEmpty()) {
-                    reason += " visible region is empty";
-                }
-            }
-            return reason;
-        }
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index 4085810..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
+++ /dev/null
@@ -1,139 +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 com.google.common.truth.Truth.assertAbout;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-
-import com.android.server.wm.flicker.Assertions.Result;
-import com.android.server.wm.flicker.LayersTrace.Entry;
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
-
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Truth subject for {@link LayersTrace} objects.
- */
-public class LayersTraceSubject extends Subject<LayersTraceSubject, LayersTrace> {
-    // Boiler-plate Subject.Factory for LayersTraceSubject
-    private static final Subject.Factory<LayersTraceSubject, LayersTrace> FACTORY =
-            new Subject.Factory<LayersTraceSubject, LayersTrace>() {
-                @Override
-                public LayersTraceSubject createSubject(
-                        FailureMetadata fm, @Nullable LayersTrace target) {
-                    return new LayersTraceSubject(fm, target);
-                }
-            };
-
-    private AssertionsChecker<Entry> mChecker = new AssertionsChecker<>();
-
-    private LayersTraceSubject(FailureMetadata fm, @Nullable LayersTrace subject) {
-        super(fm, subject);
-    }
-
-    // User-defined entry point
-    public static LayersTraceSubject assertThat(@Nullable LayersTrace entry) {
-        return assertAbout(FACTORY).that(entry);
-    }
-
-    // User-defined entry point
-    public static LayersTraceSubject assertThat(@Nullable TransitionResult result) {
-        LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(),
-                result.getLayersTracePath());
-        return assertWithMessage(result.toString()).about(FACTORY).that(entries);
-    }
-
-    // Static method for getting the subject factory (for use with assertAbout())
-    public static Subject.Factory<LayersTraceSubject, LayersTrace> entries() {
-        return FACTORY;
-    }
-
-    public void forAllEntries() {
-        test();
-    }
-
-    public void forRange(long startTime, long endTime) {
-        mChecker.filterByRange(startTime, endTime);
-        test();
-    }
-
-    public LayersTraceSubject then() {
-        mChecker.checkChangingAssertions();
-        return this;
-    }
-
-    public void inTheBeginning() {
-        if (getSubject().getEntries().isEmpty()) {
-            fail("No entries found.");
-        }
-        mChecker.checkFirstEntry();
-        test();
-    }
-
-    public void atTheEnd() {
-        if (getSubject().getEntries().isEmpty()) {
-            fail("No entries found.");
-        }
-        mChecker.checkLastEntry();
-        test();
-    }
-
-    private void test() {
-        List<Result> failures = mChecker.test(getSubject().getEntries());
-        if (!failures.isEmpty()) {
-            String failureLogs = failures.stream().map(Result::toString)
-                    .collect(Collectors.joining("\n"));
-            String tracePath = "";
-            if (getSubject().getSource().isPresent()) {
-                tracePath = "\nLayers Trace can be found in: "
-                        + getSubject().getSource().get().toAbsolutePath() + "\n";
-            }
-            fail(tracePath + failureLogs);
-        }
-    }
-
-    public LayersTraceSubject coversRegion(Rect rect) {
-        mChecker.add(entry -> entry.coversRegion(rect),
-                "coversRegion(" + rect + ")");
-        return this;
-    }
-
-    public LayersTraceSubject hasVisibleRegion(String layerName, Rect size) {
-        mChecker.add(entry -> entry.hasVisibleRegion(layerName, size),
-                "hasVisibleRegion(" + layerName + size + ")");
-        return this;
-    }
-
-    public LayersTraceSubject showsLayer(String layerName) {
-        mChecker.add(entry -> entry.isVisible(layerName),
-                "showsLayer(" + layerName + ")");
-        return this;
-    }
-
-    public LayersTraceSubject hidesLayer(String layerName) {
-        mChecker.add(entry -> entry.isVisible(layerName).negate(),
-                "hidesLayer(" + layerName + ")");
-        return this;
-    }
-}
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
deleted file mode 100644
index 0a3fe3c..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
+++ /dev/null
@@ -1,424 +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 android.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.server.wm.flicker.monitor.ITransitionMonitor;
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
-import com.android.server.wm.flicker.monitor.ScreenRecorder;
-import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
-
-import com.google.common.io.Files;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Builds and runs UI transitions capturing test artifacts.
- *
- * User can compose a transition from simpler steps, specifying setup and teardown steps. During
- * a transition, Layers trace, WindowManager trace, screen recordings and window animation frame
- * stats can be captured.
- *
- * <pre>
- * Transition builder options:
- *  {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started
- *  before the transition and stopped after the transition is completed.
- *  {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording
- *  result for each run.
- *  {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and
- *  artifacts generated.
- *  {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other
- *  transition are run to set up an initial state on device.
- *  {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition
- *  run.
- *  {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test
- *  transition.
- *  {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all
- *  other transition  are run.
- *  {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor}
- *  to monitor janky frames. If janky frames are detected, then the test run is skipped. This
- *  monitor is enabled by default.
- *  {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to
- *  capture Layers trace during a transition. This monitor is enabled by default.
- *  {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor}
- *  used to capture WindowManager trace during a transition. This monitor is enabled by
- *  default.
- *  {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file.
- *  All the runs including setup and teardown transitions are included in the recording. This
- *  monitor is used for debugging purposes.
- *  {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions
- *  and saves it to a file for each run. This monitor is used for debugging purposes.
- *
- * Example transition to capture WindowManager and Layers trace when opening a test app:
- * {@code
- * TransitionRunner.newBuilder()
- *      .withTag("OpenTestAppFast")
- *      .runBeforeAll(UiAutomationLib::wakeUp)
- *      .runBeforeAll(UiAutomationLib::UnlockDevice)
- *      .runBeforeAll(UiAutomationLib::openTestApp)
- *      .runBefore(UiAutomationLib::closeTestApp)
- *      .run(UiAutomationLib::openTestApp)
- *      .runAfterAll(UiAutomationLib::closeTestApp)
- *      .repeat(5)
- *      .build()
- *      .run();
- * }
- * </pre>
- */
-class TransitionRunner {
-    private static final String TAG = "FLICKER";
-    private final ScreenRecorder mScreenRecorder;
-    private final WindowManagerTraceMonitor mWmTraceMonitor;
-    private final LayersTraceMonitor mLayersTraceMonitor;
-    private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
-
-    private final List<ITransitionMonitor> mAllRunsMonitors;
-    private final List<ITransitionMonitor> mPerRunMonitors;
-    private final List<Runnable> mBeforeAlls;
-    private final List<Runnable> mBefores;
-    private final List<Runnable> mTransitions;
-    private final List<Runnable> mAfters;
-    private final List<Runnable> mAfterAlls;
-
-    private final int mIterations;
-    private final String mTestTag;
-
-    @Nullable
-    private List<TransitionResult> mResults = null;
-
-    private TransitionRunner(TransitionBuilder builder) {
-        mScreenRecorder = builder.mScreenRecorder;
-        mWmTraceMonitor = builder.mWmTraceMonitor;
-        mLayersTraceMonitor = builder.mLayersTraceMonitor;
-        mFrameStatsMonitor = builder.mFrameStatsMonitor;
-
-        mAllRunsMonitors = builder.mAllRunsMonitors;
-        mPerRunMonitors = builder.mPerRunMonitors;
-        mBeforeAlls = builder.mBeforeAlls;
-        mBefores = builder.mBefores;
-        mTransitions = builder.mTransitions;
-        mAfters = builder.mAfters;
-        mAfterAlls = builder.mAfterAlls;
-
-        mIterations = builder.mIterations;
-        mTestTag = builder.mTestTag;
-    }
-
-    static TransitionBuilder newBuilder() {
-        return new TransitionBuilder();
-    }
-
-    /**
-     * Runs the composed transition and calls monitors at the appropriate stages. If jank monitor
-     * is enabled, transitions with jank are skipped.
-     *
-     * @return itself
-     */
-    TransitionRunner run() {
-        mResults = new ArrayList<>();
-        mAllRunsMonitors.forEach(ITransitionMonitor::start);
-        mBeforeAlls.forEach(Runnable::run);
-        for (int iteration = 0; iteration < mIterations; iteration++) {
-            mBefores.forEach(Runnable::run);
-            mPerRunMonitors.forEach(ITransitionMonitor::start);
-            mTransitions.forEach(Runnable::run);
-            mPerRunMonitors.forEach(ITransitionMonitor::stop);
-            mAfters.forEach(Runnable::run);
-            if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) {
-                String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s",
-                        iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString());
-                Log.e(TAG, msg);
-                continue;
-            }
-            mResults.add(saveResult(iteration));
-        }
-        mAfterAlls.forEach(Runnable::run);
-        mAllRunsMonitors.forEach(monitor -> {
-            monitor.stop();
-            Path path = monitor.save(mTestTag);
-            Log.e(TAG, "Video saved to " + path.toString());
-        });
-        return this;
-    }
-
-    /**
-     * Returns a list of transition results.
-     *
-     * @return list of transition results.
-     */
-    List<TransitionResult> getResults() {
-        if (mResults == null) {
-            throw new IllegalStateException("Results do not exist!");
-        }
-        return mResults;
-    }
-
-    /**
-     * Deletes all transition results that are not marked for saving.
-     *
-     * @return list of transition results.
-     */
-    void deleteResults() {
-        if (mResults == null) {
-            return;
-        }
-        mResults.stream()
-                .filter(TransitionResult::canDelete)
-                .forEach(TransitionResult::delete);
-        mResults = null;
-    }
-
-    /**
-     * Saves monitor results to file.
-     *
-     * @return object containing paths to test artifacts
-     */
-    private TransitionResult saveResult(int iteration) {
-        Path windowTrace = null;
-        Path layerTrace = null;
-        Path screenCaptureVideo = null;
-
-        if (mPerRunMonitors.contains(mWmTraceMonitor)) {
-            windowTrace = mWmTraceMonitor.save(mTestTag, iteration);
-        }
-        if (mPerRunMonitors.contains(mLayersTraceMonitor)) {
-            layerTrace = mLayersTraceMonitor.save(mTestTag, iteration);
-        }
-        if (mPerRunMonitors.contains(mScreenRecorder)) {
-            screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration);
-        }
-        return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo);
-    }
-
-    private boolean runJankFree() {
-        return mPerRunMonitors.contains(mFrameStatsMonitor);
-    }
-
-    public String getTestTag() {
-        return mTestTag;
-    }
-
-    /**
-     * Stores paths to all test artifacts.
-     */
-    @VisibleForTesting
-    public static class TransitionResult {
-        @Nullable
-        final Path layersTrace;
-        @Nullable
-        final Path windowManagerTrace;
-        @Nullable
-        final Path screenCaptureVideo;
-        private boolean flaggedForSaving;
-
-        TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
-                @Nullable Path screenCaptureVideo) {
-            this.layersTrace = layersTrace;
-            this.windowManagerTrace = windowManagerTrace;
-            this.screenCaptureVideo = screenCaptureVideo;
-        }
-
-        void flagForSaving() {
-            flaggedForSaving = true;
-        }
-
-        boolean canDelete() {
-            return !flaggedForSaving;
-        }
-
-        boolean layersTraceExists() {
-            return layersTrace != null && layersTrace.toFile().exists();
-        }
-
-        byte[] getLayersTrace() {
-            try {
-                return Files.toByteArray(this.layersTrace.toFile());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        Path getLayersTracePath() {
-            return layersTrace;
-        }
-
-        boolean windowManagerTraceExists() {
-            return windowManagerTrace != null && windowManagerTrace.toFile().exists();
-        }
-
-        public byte[] getWindowManagerTrace() {
-            try {
-                return Files.toByteArray(this.windowManagerTrace.toFile());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        Path getWindowManagerTracePath() {
-            return windowManagerTrace;
-        }
-
-        boolean screenCaptureVideoExists() {
-            return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
-        }
-
-        Path screenCaptureVideoPath() {
-            return screenCaptureVideo;
-        }
-
-        void delete() {
-            if (layersTraceExists()) layersTrace.toFile().delete();
-            if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
-            if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
-        }
-    }
-
-    /**
-     * Builds a {@link TransitionRunner} instance.
-     */
-    static class TransitionBuilder {
-        private ScreenRecorder mScreenRecorder;
-        private WindowManagerTraceMonitor mWmTraceMonitor;
-        private LayersTraceMonitor mLayersTraceMonitor;
-        private WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
-
-        private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>();
-        private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>();
-        private List<Runnable> mBeforeAlls = new LinkedList<>();
-        private List<Runnable> mBefores = new LinkedList<>();
-        private List<Runnable> mTransitions = new LinkedList<>();
-        private List<Runnable> mAfters = new LinkedList<>();
-        private List<Runnable> mAfterAlls = new LinkedList<>();
-
-        private boolean mRunJankFree = true;
-        private boolean mCaptureWindowManagerTrace = true;
-        private boolean mCaptureLayersTrace = true;
-        private boolean mRecordEachRun = false;
-        private int mIterations = 1;
-        private String mTestTag = "";
-
-        private boolean mRecordAllRuns = false;
-
-        TransitionBuilder() {
-            mScreenRecorder = new ScreenRecorder();
-            mWmTraceMonitor = new WindowManagerTraceMonitor();
-            mLayersTraceMonitor = new LayersTraceMonitor();
-            mFrameStatsMonitor = new
-                    WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation());
-        }
-
-        TransitionRunner build() {
-            if (mCaptureWindowManagerTrace) {
-                mPerRunMonitors.add(mWmTraceMonitor);
-            }
-
-            if (mCaptureLayersTrace) {
-                mPerRunMonitors.add(mLayersTraceMonitor);
-            }
-
-            if (mRunJankFree) {
-                mPerRunMonitors.add(mFrameStatsMonitor);
-            }
-
-            if (mRecordAllRuns) {
-                mAllRunsMonitors.add(mScreenRecorder);
-            }
-
-            if (mRecordEachRun) {
-                mPerRunMonitors.add(mScreenRecorder);
-            }
-
-            return new TransitionRunner(this);
-        }
-
-        TransitionBuilder runBeforeAll(Runnable runnable) {
-            mBeforeAlls.add(runnable);
-            return this;
-        }
-
-        TransitionBuilder runBefore(Runnable runnable) {
-            mBefores.add(runnable);
-            return this;
-        }
-
-        TransitionBuilder run(Runnable runnable) {
-            mTransitions.add(runnable);
-            return this;
-        }
-
-        TransitionBuilder runAfter(Runnable runnable) {
-            mAfters.add(runnable);
-            return this;
-        }
-
-        TransitionBuilder runAfterAll(Runnable runnable) {
-            mAfterAlls.add(runnable);
-            return this;
-        }
-
-        TransitionBuilder repeat(int iterations) {
-            mIterations = iterations;
-            return this;
-        }
-
-        TransitionBuilder skipWindowManagerTrace() {
-            mCaptureWindowManagerTrace = false;
-            return this;
-        }
-
-        TransitionBuilder skipLayersTrace() {
-            mCaptureLayersTrace = false;
-            return this;
-        }
-
-        TransitionBuilder includeJankyRuns() {
-            mRunJankFree = false;
-            return this;
-        }
-
-        TransitionBuilder recordEachRun() {
-            if (mRecordAllRuns) {
-                throw new IllegalArgumentException("Invalid option with recordAllRuns");
-            }
-            mRecordEachRun = true;
-            return this;
-        }
-
-        TransitionBuilder recordAllRuns() {
-            if (mRecordEachRun) {
-                throw new IllegalArgumentException("Invalid option with recordEachRun");
-            }
-            mRecordAllRuns = true;
-            return this;
-        }
-
-        TransitionBuilder withTag(String testTag) {
-            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
deleted file mode 100644
index e3592eb..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
+++ /dev/null
@@ -1,240 +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 android.annotation.Nullable;
-
-import com.android.server.wm.flicker.Assertions.Result;
-import com.android.server.wm.nano.AppWindowTokenProto;
-import com.android.server.wm.nano.StackProto;
-import com.android.server.wm.nano.TaskProto;
-import com.android.server.wm.nano.WindowManagerTraceFileProto;
-import com.android.server.wm.nano.WindowManagerTraceProto;
-import com.android.server.wm.nano.WindowStateProto;
-import com.android.server.wm.nano.WindowTokenProto;
-
-import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
-
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Contains a collection of parsed WindowManager trace entries and assertions to apply over
- * a single entry.
- *
- * Each entry is parsed into a list of {@link WindowManagerTrace.Entry} objects.
- */
-public class WindowManagerTrace {
-    private static final int DEFAULT_DISPLAY = 0;
-    private final List<Entry> mEntries;
-    @Nullable
-    final private Path mSource;
-
-    private WindowManagerTrace(List<Entry> entries, Path source) {
-        this.mEntries = entries;
-        this.mSource = source;
-    }
-
-    /**
-     * Parses {@code WindowManagerTraceFileProto} from {@code data} and uses the proto to
-     * generates a list of trace entries.
-     *
-     * @param data   binary proto data
-     * @param source Path to source of data for additional debug information
-     */
-    static WindowManagerTrace parseFrom(byte[] data, Path source) {
-        List<Entry> entries = new ArrayList<>();
-
-        WindowManagerTraceFileProto fileProto;
-        try {
-            fileProto = WindowManagerTraceFileProto.parseFrom(data);
-        } catch (InvalidProtocolBufferNanoException e) {
-            throw new RuntimeException(e);
-        }
-        for (WindowManagerTraceProto entryProto : fileProto.entry) {
-            entries.add(new Entry(entryProto));
-        }
-        return new WindowManagerTrace(entries, source);
-    }
-
-    static WindowManagerTrace parseFrom(byte[] data) {
-        return parseFrom(data, null);
-    }
-
-    public List<Entry> getEntries() {
-        return mEntries;
-    }
-
-    Entry getEntry(long timestamp) {
-        Optional<Entry> entry = mEntries.stream()
-                .filter(e -> e.getTimestamp() == timestamp)
-                .findFirst();
-        if (!entry.isPresent()) {
-            throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
-        }
-        return entry.get();
-    }
-
-    Optional<Path> getSource() {
-        return Optional.ofNullable(mSource);
-    }
-
-    /**
-     * Represents a single WindowManager trace entry.
-     */
-    static class Entry implements ITraceEntry {
-        private final WindowManagerTraceProto mProto;
-
-        Entry(WindowManagerTraceProto proto) {
-            mProto = proto;
-        }
-
-        private static Result isWindowVisible(String windowTitle,
-                WindowTokenProto[] windowTokenProtos) {
-            boolean titleFound = false;
-            for (WindowTokenProto windowToken : windowTokenProtos) {
-                for (WindowStateProto windowState : windowToken.windows) {
-                    if (windowState.identifier.title.contains(windowTitle)) {
-                        titleFound = true;
-                        if (isVisible(windowState)) {
-                            return new Result(true /* success */,
-                                    windowState.identifier.title + " is visible");
-                        }
-                    }
-                }
-            }
-
-            String reason;
-            if (!titleFound) {
-                reason = windowTitle + " cannot be found";
-            } else {
-                reason = windowTitle + " is invisible";
-            }
-            return new Result(false /* success */, reason);
-        }
-
-        private static boolean isVisible(WindowStateProto windowState) {
-            return windowState.windowContainer.visible;
-        }
-
-        @Override
-        public long getTimestamp() {
-            return mProto.elapsedRealtimeNanos;
-        }
-
-        /**
-         * Returns window title of the top most visible app window.
-         */
-        private String getTopVisibleAppWindow() {
-            StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
-                    .displays[DEFAULT_DISPLAY].stacks;
-            for (StackProto stack : stacks) {
-                for (TaskProto task : stack.tasks) {
-                    for (AppWindowTokenProto token : task.appWindowTokens) {
-                        for (WindowStateProto windowState : token.windowToken.windows) {
-                            if (windowState.windowContainer.visible) {
-                                return task.appWindowTokens[0].name;
-                            }
-                        }
-                    }
-                }
-            }
-
-            return "";
-        }
-
-        /**
-         * Checks if aboveAppWindow with {@code windowTitle} is visible.
-         */
-        Result isAboveAppWindowVisible(String windowTitle) {
-            WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
-                    .rootWindowContainer
-                    .displays[DEFAULT_DISPLAY].aboveAppWindows;
-            Result result = isWindowVisible(windowTitle, windowTokenProtos);
-            return new Result(result.success, getTimestamp(), "showsAboveAppWindow", result.reason);
-        }
-
-        /**
-         * Checks if belowAppWindow with {@code windowTitle} is visible.
-         */
-        Result isBelowAppWindowVisible(String windowTitle) {
-            WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
-                    .rootWindowContainer
-                    .displays[DEFAULT_DISPLAY].belowAppWindows;
-            Result result = isWindowVisible(windowTitle, windowTokenProtos);
-            return new Result(result.success, getTimestamp(), "isBelowAppWindowVisible",
-                    result.reason);
-        }
-
-        /**
-         * Checks if imeWindow with {@code windowTitle} is visible.
-         */
-        Result isImeWindowVisible(String windowTitle) {
-            WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
-                    .rootWindowContainer
-                    .displays[DEFAULT_DISPLAY].imeWindows;
-            Result result = isWindowVisible(windowTitle, windowTokenProtos);
-            return new Result(result.success, getTimestamp(), "isImeWindowVisible",
-                    result.reason);
-        }
-
-        /**
-         * Checks if app window with {@code windowTitle} is on top.
-         */
-        Result isVisibleAppWindowOnTop(String windowTitle) {
-            String topAppWindow = getTopVisibleAppWindow();
-            boolean success = topAppWindow.contains(windowTitle);
-            String reason = "wanted=" + windowTitle + " found=" + topAppWindow;
-            return new Result(success, getTimestamp(), "isAppWindowOnTop", reason);
-        }
-
-        /**
-         * Checks if app window with {@code windowTitle} is visible.
-         */
-        Result isAppWindowVisible(String windowTitle) {
-            final String assertionName = "isAppWindowVisible";
-            boolean titleFound = false;
-            StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
-                    .displays[DEFAULT_DISPLAY].stacks;
-            for (StackProto stack : stacks) {
-                for (TaskProto task : stack.tasks) {
-                    for (AppWindowTokenProto token : task.appWindowTokens) {
-                        if (token.name.contains(windowTitle)) {
-                            titleFound = true;
-                            for (WindowStateProto windowState : token.windowToken.windows) {
-                                if (windowState.windowContainer.visible) {
-                                    return new Result(true /* success */, getTimestamp(),
-                                            assertionName, "Window " + token.name +
-                                            "is visible");
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            String reason;
-            if (!titleFound) {
-                reason = "Window " + windowTitle + " cannot be found";
-            } else {
-                reason = "Window " + windowTitle + " is invisible";
-            }
-            return new Result(false /* success */, getTimestamp(), assertionName, reason);
-        }
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index c54396f..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
+++ /dev/null
@@ -1,144 +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 android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Surface;
-import android.view.WindowManager;
-
-import androidx.test.InstrumentationRegistry;
-
-/**
- * Helper functions to retrieve system window sizes and positions.
- */
-class WindowUtils {
-
-    static Rect getDisplayBounds() {
-        Point display = new Point();
-        WindowManager wm =
-                (WindowManager) InstrumentationRegistry.getContext().getSystemService(
-                        Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getRealSize(display);
-        return new Rect(0, 0, display.x, display.y);
-    }
-
-    private static int getCurrentRotation() {
-        WindowManager wm =
-                (WindowManager) InstrumentationRegistry.getContext().getSystemService(
-                        Context.WINDOW_SERVICE);
-        return wm.getDefaultDisplay().getRotation();
-    }
-
-    static Rect getDisplayBounds(int requestedRotation) {
-        Rect displayBounds = getDisplayBounds();
-        int currentDisplayRotation = getCurrentRotation();
-
-        boolean displayIsRotated = (currentDisplayRotation == Surface.ROTATION_90 ||
-                currentDisplayRotation == Surface.ROTATION_270);
-
-        boolean requestedDisplayIsRotated = requestedRotation == Surface.ROTATION_90 ||
-                requestedRotation == Surface.ROTATION_270;
-
-        // if the current orientation changes with the requested rotation,
-        // flip height and width of display bounds.
-        if (displayIsRotated != requestedDisplayIsRotated) {
-            return new Rect(0, 0, displayBounds.height(), displayBounds.width());
-        }
-
-        return new Rect(0, 0, displayBounds.width(), displayBounds.height());
-    }
-
-
-    static Rect getAppPosition(int requestedRotation) {
-        Rect displayBounds = getDisplayBounds();
-        int currentDisplayRotation = getCurrentRotation();
-
-        boolean displayIsRotated = currentDisplayRotation == Surface.ROTATION_90 ||
-                currentDisplayRotation == Surface.ROTATION_270;
-
-        boolean requestedAppIsRotated = requestedRotation == Surface.ROTATION_90 ||
-                requestedRotation == Surface.ROTATION_270;
-
-        // display size will change if the display is reflected. Flip height and width of app if the
-        // requested rotation is different from the current rotation.
-        if (displayIsRotated != requestedAppIsRotated) {
-            return new Rect(0, 0, displayBounds.height(), displayBounds.width());
-        }
-
-        return new Rect(0, 0, displayBounds.width(), displayBounds.height());
-    }
-
-    static Rect getStatusBarPosition(int requestedRotation) {
-        Resources resources = InstrumentationRegistry.getContext().getResources();
-        String resourceName;
-        Rect displayBounds = getDisplayBounds();
-        int width;
-        if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
-            resourceName = "status_bar_height_portrait";
-            width = Math.min(displayBounds.width(), displayBounds.height());
-        } else {
-            resourceName = "status_bar_height_landscape";
-            width = Math.max(displayBounds.width(), displayBounds.height());
-        }
-
-        int resourceId = resources.getIdentifier(resourceName, "dimen", "android");
-        int height = resources.getDimensionPixelSize(resourceId);
-
-        return new Rect(0, 0, width, height);
-    }
-
-    static Rect getNavigationBarPosition(int requestedRotation) {
-        Resources resources = InstrumentationRegistry.getContext().getResources();
-        Rect displayBounds = getDisplayBounds();
-        int displayWidth = Math.min(displayBounds.width(), displayBounds.height());
-        int displayHeight = Math.max(displayBounds.width(), displayBounds.height());
-        int resourceId;
-        if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
-            resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
-            int height = resources.getDimensionPixelSize(resourceId);
-            return new Rect(0, displayHeight - height, displayWidth, displayHeight);
-        } else {
-            resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android");
-            int width = resources.getDimensionPixelSize(resourceId);
-            // swap display dimensions in landscape or seascape mode
-            int temp = displayHeight;
-            displayHeight = displayWidth;
-            displayWidth = temp;
-            if (requestedRotation == Surface.ROTATION_90) {
-                return new Rect(0, 0, width, displayHeight);
-            } else {
-                return new Rect(displayWidth - width, 0, displayWidth, displayHeight);
-            }
-        }
-    }
-
-    static int getNavigationBarHeight() {
-        Resources resources = InstrumentationRegistry.getContext().getResources();
-        int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
-        return resources.getDimensionPixelSize(resourceId);
-    }
-
-    static int getDockedStackDividerInset() {
-        Resources resources = InstrumentationRegistry.getContext().getResources();
-        int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen",
-                "android");
-        return resources.getDimensionPixelSize(resourceId);
-    }
-}
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
deleted file mode 100644
index e76da6e..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
+++ /dev/null
@@ -1,192 +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 com.google.common.truth.Truth.assertAbout;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.annotation.Nullable;
-
-import com.android.server.wm.flicker.Assertions.Result;
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
-
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/**
- * Truth subject for {@link WindowManagerTrace} objects.
- */
-public class WmTraceSubject extends Subject<WmTraceSubject, WindowManagerTrace> {
-    // Boiler-plate Subject.Factory for WmTraceSubject
-    private static final Subject.Factory<WmTraceSubject, WindowManagerTrace> FACTORY =
-            new Subject.Factory<WmTraceSubject, WindowManagerTrace>() {
-                @Override
-                public WmTraceSubject createSubject(
-                        FailureMetadata fm, @Nullable WindowManagerTrace target) {
-                    return new WmTraceSubject(fm, target);
-                }
-            };
-
-    private AssertionsChecker<WindowManagerTrace.Entry> mChecker = new AssertionsChecker<>();
-
-    private WmTraceSubject(FailureMetadata fm, @Nullable WindowManagerTrace subject) {
-        super(fm, subject);
-    }
-
-    // User-defined entry point
-    public static WmTraceSubject assertThat(@Nullable WindowManagerTrace entry) {
-        return assertAbout(FACTORY).that(entry);
-    }
-
-    // User-defined entry point
-    public static WmTraceSubject assertThat(@Nullable TransitionResult result) {
-        WindowManagerTrace entries = WindowManagerTrace.parseFrom(result.getWindowManagerTrace(),
-                result.getWindowManagerTracePath());
-        return assertWithMessage(result.toString()).about(FACTORY).that(entries);
-    }
-
-    // Static method for getting the subject factory (for use with assertAbout())
-    public static Subject.Factory<WmTraceSubject, WindowManagerTrace> entries() {
-        return FACTORY;
-    }
-
-    public void forAllEntries() {
-        test();
-    }
-
-    public void forRange(long startTime, long endTime) {
-        mChecker.filterByRange(startTime, endTime);
-        test();
-    }
-
-    public WmTraceSubject then() {
-        mChecker.checkChangingAssertions();
-        return this;
-    }
-
-    public void inTheBeginning() {
-        if (getSubject().getEntries().isEmpty()) {
-            fail("No entries found.");
-        }
-        mChecker.checkFirstEntry();
-        test();
-    }
-
-    public void atTheEnd() {
-        if (getSubject().getEntries().isEmpty()) {
-            fail("No entries found.");
-        }
-        mChecker.checkLastEntry();
-        test();
-    }
-
-    private void test() {
-        List<Result> failures = mChecker.test(getSubject().getEntries());
-        if (!failures.isEmpty()) {
-            Optional<Path> failureTracePath = getSubject().getSource();
-            String failureLogs = failures.stream().map(Result::toString)
-                    .collect(Collectors.joining("\n"));
-            String tracePath = "";
-            if (failureTracePath.isPresent()) {
-                tracePath = "\nWindowManager Trace can be found in: "
-                        + failureTracePath.get().toAbsolutePath() + "\n";
-            }
-            fail(tracePath + failureLogs);
-        }
-    }
-
-    public WmTraceSubject showsAboveAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle),
-                "showsAboveAppWindow(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject hidesAboveAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle).negate(),
-                "hidesAboveAppWindow" + "(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject showsBelowAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle),
-                "showsBelowAppWindow(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject hidesBelowAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle).negate(),
-                "hidesBelowAppWindow" + "(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject showsImeWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle),
-                "showsBelowAppWindow(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject hidesImeWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle).negate(),
-                "hidesImeWindow" + "(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject showsAppWindowOnTop(String partialWindowTitle) {
-        mChecker.add(
-                entry -> {
-                    Result result = entry.isAppWindowVisible(partialWindowTitle);
-                    if (result.passed()) {
-                        result = entry.isVisibleAppWindowOnTop(partialWindowTitle);
-                    }
-                    return result;
-                },
-                "showsAppWindowOnTop(" + partialWindowTitle + ")"
-        );
-        return this;
-    }
-
-    public WmTraceSubject hidesAppWindowOnTop(String partialWindowTitle) {
-        mChecker.add(
-                entry -> {
-                    Result result = entry.isAppWindowVisible(partialWindowTitle).negate();
-                    if (result.failed()) {
-                        result = entry.isVisibleAppWindowOnTop(partialWindowTitle).negate();
-                    }
-                    return result;
-                },
-                "hidesAppWindowOnTop(" + partialWindowTitle + ")"
-        );
-        return this;
-    }
-
-    public WmTraceSubject showsAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle),
-                "showsAppWindow(" + partialWindowTitle + ")");
-        return this;
-    }
-
-    public WmTraceSubject hidesAppWindow(String partialWindowTitle) {
-        mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle).negate(),
-                "hidesAppWindow(" + partialWindowTitle + ")");
-        return this;
-    }
-}
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java
deleted file mode 100644
index 67e0ecc..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ITransitionMonitor.java
+++ /dev/null
@@ -1,63 +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.monitor;
-
-import android.os.Environment;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-/**
- * Collects test artifacts during a UI transition.
- */
-public interface ITransitionMonitor {
-    Path OUTPUT_DIR = Paths.get(Environment.getExternalStorageDirectory().toString(), "flicker");
-
-    /**
-     * Starts monitor.
-     */
-    void start();
-
-    /**
-     * Stops monitor.
-     */
-    void stop();
-
-    /**
-     * Saves any monitor artifacts to file adding {@code testTag} and {@code iteration}
-     * to the file name.
-     *
-     * @param testTag   suffix added to artifact name
-     * @param iteration suffix added to artifact name
-     *
-     * @return Path to saved artifact
-     */
-    default Path save(String testTag, int iteration) {
-        return save(testTag + "_" + iteration);
-    }
-
-    /**
-     * Saves any monitor artifacts to file adding {@code testTag} to the file name.
-     *
-     * @param testTag suffix added to artifact name
-     *
-     * @return Path to saved artifact
-     */
-    default Path save(String testTag) {
-        throw new UnsupportedOperationException("Save not implemented for this monitor");
-    }
-}
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
deleted file mode 100644
index c55d068..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.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 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;
-
-/**
- * Captures Layers trace from SurfaceFlinger.
- */
-public class LayersTraceMonitor extends TraceMonitor {
-    private static final String TAG = "LayersTraceMonitor";
-    private IBinder mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
-
-    public LayersTraceMonitor() {
-        traceFileName = "layers_trace.pb";
-    }
-
-    @Override
-    public void start() {
-        setEnabled(true);
-    }
-
-    @Override
-    public void stop() {
-        setEnabled(false);
-    }
-
-    @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();
-    }
-
-    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 */);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set layer tracing." + e.toString());
-        } finally {
-            if (data != null) {
-                data.recycle();
-            }
-        }
-    }
-}
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
deleted file mode 100644
index 4787586..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
+++ /dev/null
@@ -1,78 +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.monitor;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-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");
-    private static final String TAG = "FLICKER";
-    private Thread recorderThread;
-
-    @VisibleForTesting
-    static Path getPath(String testTag) {
-        return OUTPUT_DIR.resolve(testTag + ".mp4");
-    }
-
-    @Override
-    public void start() {
-        OUTPUT_DIR.toFile().mkdirs();
-        String command = "screenrecord " + DEFAULT_OUTPUT_PATH;
-        recorderThread = new Thread(() -> {
-            try {
-                Runtime.getRuntime().exec(command);
-            } catch (IOException e) {
-                Log.e(TAG, "Error executing " + command, e);
-            }
-        });
-        recorderThread.start();
-    }
-
-    @Override
-    public void stop() {
-        runShellCommand("killall -s 2 screenrecord");
-        try {
-            recorderThread.join();
-        } catch (InterruptedException e) {
-            // ignore
-        }
-    }
-
-    @Override
-    public Path save(String testTag) {
-        try {
-            return Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
-                    REPLACE_EXISTING);
-        } 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
deleted file mode 100644
index 0e154ec..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
+++ /dev/null
@@ -1,66 +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.monitor;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
-import android.os.RemoteException;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Locale;
-
-/**
- * Base class for monitors containing common logic to read the trace
- * as a byte array and save the trace to another location.
- */
-public abstract class TraceMonitor implements ITransitionMonitor {
-    public static final String TAG = "FLICKER";
-    private static final String TRACE_DIR = "/data/misc/wmtrace/";
-
-    String traceFileName;
-
-    abstract boolean isEnabled() throws RemoteException;
-
-    /**
-     * Saves trace file to the external storage directory suffixing the name with the testtag
-     * and iteration.
-     *
-     * Moves the trace file from the default location via a shell command since the test app
-     * does not have security privileges to access /data/misc/wmtrace.
-     *
-     * @param testTag suffix added to trace name used to identify trace
-     *
-     * @return Path to saved trace file
-     */
-    @Override
-    public Path save(String testTag) {
-        OUTPUT_DIR.toFile().mkdirs();
-        Path traceFileCopy = getOutputTraceFilePath(testTag);
-        String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR,
-                traceFileName, traceFileCopy.toString());
-        runShellCommand(copyCommand);
-        return traceFileCopy;
-    }
-
-    @VisibleForTesting
-    Path getOutputTraceFilePath(String testTag) {
-        return OUTPUT_DIR.resolve(traceFileName + "_" + testTag);
-    }
-}
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.java
deleted file mode 100644
index 3f86f0d..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitor.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.wm.flicker.monitor;
-
-import static android.view.FrameStats.UNDEFINED_TIME_NANO;
-
-import android.app.Instrumentation;
-import android.util.Log;
-import android.view.FrameStats;
-
-/**
- * Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames.
- *
- * Adapted from {@link androidx.test.jank.internal.WindowAnimationFrameStatsMonitorImpl}
- * using the same threshold to determine jank.
- */
-public class WindowAnimationFrameStatsMonitor implements ITransitionMonitor {
-
-    private static final String TAG = "FLICKER";
-    // Maximum normalized error in frame duration before the frame is considered janky
-    private static final double MAX_ERROR = 0.5f;
-    // Maximum normalized frame duration before the frame is considered a pause
-    private static final double PAUSE_THRESHOLD = 15.0f;
-    private Instrumentation mInstrumentation;
-    private FrameStats stats;
-    private int numJankyFrames;
-    private long mLongestFrameNano = 0L;
-
-
-    /**
-     * Constructs a WindowAnimationFrameStatsMonitor instance.
-     */
-    public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) {
-        mInstrumentation = instrumentation;
-    }
-
-    private void analyze() {
-        int frameCount = stats.getFrameCount();
-        long refreshPeriodNano = stats.getRefreshPeriodNano();
-
-        // Skip first frame
-        for (int i = 2; i < frameCount; i++) {
-            // Handle frames that have not been presented.
-            if (stats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) {
-                // The animation must not have completed. Warn and break out of the loop.
-                Log.w(TAG, "Skipping fenced frame.");
-                break;
-            }
-            long frameDurationNano = stats.getFramePresentedTimeNano(i) -
-                    stats.getFramePresentedTimeNano(i - 1);
-            double normalized = (double) frameDurationNano / refreshPeriodNano;
-            if (normalized < PAUSE_THRESHOLD) {
-                if (normalized > 1.0f + MAX_ERROR) {
-                    numJankyFrames++;
-                }
-                mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano);
-            }
-        }
-    }
-
-    @Override
-    public void start() {
-        // Clear out any previous data
-        numJankyFrames = 0;
-        mLongestFrameNano = 0;
-        mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats();
-    }
-
-    @Override
-    public void stop() {
-        stats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats();
-        analyze();
-    }
-
-    public boolean jankyFramesDetected() {
-        return stats.getFrameCount() > 0 && numJankyFrames > 0;
-    }
-
-    @Override
-    public String toString() {
-        return stats.toString() +
-                " RefreshPeriodNano:" + stats.getRefreshPeriodNano() +
-                " NumJankyFrames:" + numJankyFrames +
-                " LongestFrameNano:" + mLongestFrameNano;
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index ae160b68..0000000
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
+++ /dev/null
@@ -1,55 +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.monitor;
-
-import android.os.RemoteException;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
-
-/**
- * Captures WindowManager trace from WindowManager.
- */
-public class WindowManagerTraceMonitor extends TraceMonitor {
-    private IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-
-    public WindowManagerTraceMonitor() {
-        traceFileName = "wm_trace.pb";
-    }
-
-    @Override
-    public void start() {
-        try {
-            wm.startWindowTrace();
-        } catch (RemoteException e) {
-            throw new RuntimeException("Could not start trace", e);
-        }
-    }
-
-    @Override
-    public void stop() {
-        try {
-            wm.stopWindowTrace();
-        } catch (RemoteException e) {
-            throw new RuntimeException("Could not stop trace", e);
-        }
-    }
-
-    @Override
-    public boolean isEnabled() throws RemoteException{
-        return wm.isWindowTraceEnabled();
-    }
-}
diff --git a/tests/FlickerTests/lib/test/Android.bp b/tests/FlickerTests/lib/test/Android.bp
deleted file mode 100644
index bfeb75b2..0000000
--- a/tests/FlickerTests/lib/test/Android.bp
+++ /dev/null
@@ -1,33 +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.
-//
-
-android_test {
-    name: "FlickerLibTest",
-    // sign this with platform cert, so this test is allowed to call private platform apis
-    certificate: "platform",
-    platform_apis: true,
-    test_suites: ["tests"],
-    srcs: ["src/**/*.java"],
-    libs: ["android.test.runner"],
-    static_libs: [
-        "androidx.test.rules",
-        "platform-test-annotations",
-        "truth-prebuilt",
-        "platformprotosnano",
-        "layersprotosnano",
-        "flickerlib",
-    ],
-}
diff --git a/tests/FlickerTests/lib/test/AndroidManifest.xml b/tests/FlickerTests/lib/test/AndroidManifest.xml
deleted file mode 100644
index 6451a57..0000000
--- a/tests/FlickerTests/lib/test/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.server.wm.flicker">
-
-    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
-    <!-- Read and write traces from external storage -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <!-- Capture screen contents -->
-    <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
-    <!-- Run layers trace -->
-    <uses-permission android:name="android.permission.HARDWARE_TEST"/>
-    <application android:label="FlickerLibTest">
-        <uses-library android:name="android.test.runner"/>
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.server.wm.flicker"
-                     android:label="WindowManager Flicker Lib Test">
-    </instrumentation>
-
-</manifest>
\ No newline at end of file
diff --git a/tests/FlickerTests/lib/test/AndroidTest.xml b/tests/FlickerTests/lib/test/AndroidTest.xml
deleted file mode 100644
index e4cc298..0000000
--- a/tests/FlickerTests/lib/test/AndroidTest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright 2018 Google Inc. All Rights Reserved.
- -->
-<configuration description="Config for WindowManager Flicker Tests">
-    <target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup">
-        <!-- keeps the screen on during tests -->
-        <option name="screen-always-on" value="on" />
-        <!-- prevents the phone from restarting -->
-        <option name="force-skip-system-props" value="true" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true"/>
-        <option name="test-file-name" value="FlickerLibTest.apk"/>
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
-        <option name="package" value="com.android.server.wm.flicker"/>
-        <option name="hidden-api-checks" value="false" />
-    </test>
-</configuration>
diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb
deleted file mode 100644
index 98ee6f3..0000000
--- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_emptyregion.pb
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb
deleted file mode 100644
index 20572d7..0000000
--- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_invalid_layer_visibility.pb
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb b/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb
deleted file mode 100644
index af40797..0000000
--- a/tests/FlickerTests/lib/test/assets/testdata/layers_trace_orphanlayers.pb
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb
deleted file mode 100644
index b3f3170..0000000
--- a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome.pb
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb b/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb
deleted file mode 100644
index b3b73ce..0000000
--- a/tests/FlickerTests/lib/test/assets/testdata/wm_trace_openchrome2.pb
+++ /dev/null
Binary files differ
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java
deleted file mode 100644
index 8e7fe1b..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsCheckerTest.java
+++ /dev/null
@@ -1,181 +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 com.google.common.truth.Truth.assertThat;
-
-import com.android.server.wm.flicker.Assertions.Result;
-
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Contains {@link AssertionsChecker} tests.
- * To run this test: {@code atest FlickerLibTest:AssertionsCheckerTest}
- */
-public class AssertionsCheckerTest {
-
-    /**
-     * Returns a list of SimpleEntry objects with {@code data} and incremental timestamps starting
-     * at 0.
-     */
-    private static List<SimpleEntry> getTestEntries(int... data) {
-        List<SimpleEntry> entries = new ArrayList<>();
-        for (int i = 0; i < data.length; i++) {
-            entries.add(new SimpleEntry(i, data[i]));
-        }
-        return entries;
-    }
-
-    @Test
-    public void canCheckAllEntries() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.add(SimpleEntry::isData42, "isData42");
-
-        List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
-
-        assertThat(failures).hasSize(5);
-    }
-
-    @Test
-    public void canCheckFirstEntry() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.checkFirstEntry();
-        checker.add(SimpleEntry::isData42, "isData42");
-
-        List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
-
-        assertThat(failures).hasSize(1);
-        assertThat(failures.get(0).timestamp).isEqualTo(0);
-    }
-
-    @Test
-    public void canCheckLastEntry() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.checkLastEntry();
-        checker.add(SimpleEntry::isData42, "isData42");
-
-        List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
-
-        assertThat(failures).hasSize(1);
-        assertThat(failures.get(0).timestamp).isEqualTo(4);
-    }
-
-    @Test
-    public void canCheckRangeOfEntries() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.filterByRange(1, 2);
-        checker.add(SimpleEntry::isData42, "isData42");
-
-        List<Result> failures = checker.test(getTestEntries(1, 42, 42, 1, 1));
-
-        assertThat(failures).hasSize(0);
-    }
-
-    @Test
-    public void emptyRangePasses() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.filterByRange(9, 10);
-        checker.add(SimpleEntry::isData42, "isData42");
-
-        List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
-
-        assertThat(failures).isEmpty();
-    }
-
-    @Test
-    public void canCheckChangingAssertions() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.add(SimpleEntry::isData42, "isData42");
-        checker.add(SimpleEntry::isData0, "isData0");
-        checker.checkChangingAssertions();
-
-        List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
-
-        assertThat(failures).isEmpty();
-    }
-
-    @Test
-    public void canCheckChangingAssertions_withNoAssertions() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.checkChangingAssertions();
-
-        List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
-
-        assertThat(failures).isEmpty();
-    }
-
-    @Test
-    public void canCheckChangingAssertions_withSingleAssertion() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.add(SimpleEntry::isData42, "isData42");
-        checker.checkChangingAssertions();
-
-        List<Result> failures = checker.test(getTestEntries(42, 42, 42, 42, 42));
-
-        assertThat(failures).isEmpty();
-    }
-
-    @Test
-    public void canFailCheckChangingAssertions_ifStartingAssertionFails() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.add(SimpleEntry::isData42, "isData42");
-        checker.add(SimpleEntry::isData0, "isData0");
-        checker.checkChangingAssertions();
-
-        List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
-
-        assertThat(failures).hasSize(1);
-    }
-
-    @Test
-    public void canFailCheckChangingAssertions_ifStartingAssertionAlwaysPasses() {
-        AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
-        checker.add(SimpleEntry::isData42, "isData42");
-        checker.add(SimpleEntry::isData0, "isData0");
-        checker.checkChangingAssertions();
-
-        List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
-
-        assertThat(failures).hasSize(1);
-    }
-
-    static class SimpleEntry implements ITraceEntry {
-        long timestamp;
-        int data;
-
-        SimpleEntry(long timestamp, int data) {
-            this.timestamp = timestamp;
-            this.data = data;
-        }
-
-        @Override
-        public long getTimestamp() {
-            return timestamp;
-        }
-
-        Result isData42() {
-            return new Result(this.data == 42, this.timestamp, "is42", "");
-        }
-
-        Result isData0() {
-            return new Result(this.data == 0, this.timestamp, "is42", "");
-        }
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java
deleted file mode 100644
index 7fd178c..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/AssertionsTest.java
+++ /dev/null
@@ -1,60 +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 com.google.common.truth.Truth.assertThat;
-
-import com.android.server.wm.flicker.Assertions.Result;
-
-import org.junit.Test;
-
-/**
- * Contains {@link Assertions} tests.
- * To run this test: {@code atest FlickerLibTest:AssertionsTest}
- */
-public class AssertionsTest {
-    @Test
-    public void traceEntryAssertionCanNegateResult() {
-        Assertions.TraceAssertion<Integer> assertNumEquals42 =
-                getIntegerTraceEntryAssertion();
-
-        assertThat(assertNumEquals42.apply(1).success).isFalse();
-        assertThat(assertNumEquals42.negate().apply(1).success).isTrue();
-
-        assertThat(assertNumEquals42.apply(42).success).isTrue();
-        assertThat(assertNumEquals42.negate().apply(42).success).isFalse();
-    }
-
-    @Test
-    public void resultCanBeNegated() {
-        String reason = "Everything is fine!";
-        Result result = new Result(true, 0, "TestAssert", reason);
-        Result negatedResult = result.negate();
-        assertThat(negatedResult.success).isFalse();
-        assertThat(negatedResult.reason).isEqualTo(reason);
-        assertThat(negatedResult.assertionName).isEqualTo("!TestAssert");
-    }
-
-    private Assertions.TraceAssertion<Integer> getIntegerTraceEntryAssertion() {
-        return (num) -> {
-            if (num == 42) {
-                return new Result(true, "Num equals 42");
-            }
-            return new Result(false, "Num doesn't equal 42, actual:" + num);
-        };
-    }
-}
\ No newline at end of file
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java
deleted file mode 100644
index d06c5d7..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceSubjectTest.java
+++ /dev/null
@@ -1,88 +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 com.android.server.wm.flicker.LayersTraceSubject.assertThat;
-import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-
-import android.graphics.Rect;
-
-import org.junit.Test;
-
-import java.nio.file.Paths;
-
-/**
- * Contains {@link LayersTraceSubject} tests.
- * To run this test: {@code atest FlickerLibTest:LayersTraceSubjectTest}
- */
-public class LayersTraceSubjectTest {
-    private static final Rect displayRect = new Rect(0, 0, 1440, 2880);
-
-    private static LayersTrace readLayerTraceFromFile(String relativePath) {
-        try {
-            return LayersTrace.parseFrom(readTestFile(relativePath), Paths.get(relativePath));
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Test
-    public void testCanDetectEmptyRegionFromLayerTrace() {
-        LayersTrace layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb");
-        try {
-            assertThat(layersTraceEntries).coversRegion(displayRect).forAllEntries();
-            fail("Assertion passed");
-        } catch (AssertionError e) {
-            assertWithMessage("Contains path to trace")
-                    .that(e.getMessage()).contains("layers_trace_emptyregion.pb");
-            assertWithMessage("Contains timestamp")
-                    .that(e.getMessage()).contains("0h38m28s8ms");
-            assertWithMessage("Contains assertion function")
-                    .that(e.getMessage()).contains("coversRegion");
-            assertWithMessage("Contains debug info")
-                    .that(e.getMessage()).contains("Region to test: " + displayRect);
-            assertWithMessage("Contains debug info")
-                    .that(e.getMessage()).contains("first empty point: 0, 99");
-        }
-    }
-
-    @Test
-    public void testCanDetectIncorrectVisibilityFromLayerTrace() {
-        LayersTrace layersTraceEntries = readLayerTraceFromFile(
-                "layers_trace_invalid_layer_visibility.pb");
-        try {
-            assertThat(layersTraceEntries).showsLayer("com.android.server.wm.flicker.testapp")
-                    .then().hidesLayer("com.android.server.wm.flicker.testapp").forAllEntries();
-            fail("Assertion passed");
-        } catch (AssertionError e) {
-            assertWithMessage("Contains path to trace")
-                    .that(e.getMessage()).contains("layers_trace_invalid_layer_visibility.pb");
-            assertWithMessage("Contains timestamp")
-                    .that(e.getMessage()).contains("70h13m14s303ms");
-            assertWithMessage("Contains assertion function")
-                    .that(e.getMessage()).contains("!isVisible");
-            assertWithMessage("Contains debug info")
-                    .that(e.getMessage()).contains(
-                    "com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp"
-                            + ".SimpleActivity#0 is visible");
-        }
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java
deleted file mode 100644
index 7d77126..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/LayersTraceTest.java
+++ /dev/null
@@ -1,230 +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 com.android.server.wm.flicker.TestFileUtils.readTestFile;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.WindowManager;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Test;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Contains {@link LayersTrace} tests.
- * To run this test: {@code atest FlickerLibTest:LayersTraceTest}
- */
-public class LayersTraceTest {
-    private static LayersTrace readLayerTraceFromFile(String relativePath) {
-        try {
-            return LayersTrace.parseFrom(readTestFile(relativePath));
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static Rect getDisplayBounds() {
-        Point display = new Point();
-        WindowManager wm =
-                (WindowManager) InstrumentationRegistry.getContext().getSystemService(
-                        Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getRealSize(display);
-        return new Rect(0, 0, display.x, display.y);
-    }
-
-    @Test
-    public void canParseAllLayers() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        assertThat(trace.getEntries()).isNotEmpty();
-        assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
-        assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
-                .isEqualTo(2308521813510L);
-        List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
-        String msg = "Layers:\n" + flattenedLayers.stream().map(layer -> layer.mProto.name)
-                .collect(Collectors.joining("\n\t"));
-        assertWithMessage(msg).that(flattenedLayers).hasSize(47);
-    }
-
-    @Test
-    public void canParseVisibleLayers() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        assertThat(trace.getEntries()).isNotEmpty();
-        assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
-        assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
-                .isEqualTo(2308521813510L);
-        List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
-        List<LayersTrace.Layer> visibleLayers = flattenedLayers.stream()
-                .filter(layer -> layer.isVisible() && !layer.isHiddenByParent())
-                .collect(Collectors.toList());
-
-        String msg = "Visible Layers:\n" + visibleLayers.stream()
-                .map(layer -> layer.mProto.name)
-                .collect(Collectors.joining("\n\t"));
-
-        assertWithMessage(msg).that(visibleLayers).hasSize(9);
-    }
-
-    @Test
-    public void canParseLayerHierarchy() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        assertThat(trace.getEntries()).isNotEmpty();
-        assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
-        assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
-                .isEqualTo(2308521813510L);
-        List<LayersTrace.Layer> layers = trace.getEntries().get(0).getRootLayers();
-        assertThat(layers).hasSize(2);
-        assertThat(layers.get(0).mChildren).hasSize(layers.get(0).mProto.children.length);
-        assertThat(layers.get(1).mChildren).hasSize(layers.get(1).mProto.children.length);
-    }
-
-    // b/76099859
-    @Test
-    public void canDetectOrphanLayers() {
-        try {
-            readLayerTraceFromFile(
-                    "layers_trace_orphanlayers.pb");
-            fail("Failed to detect orphaned layers.");
-        } catch (RuntimeException exception) {
-            assertThat(exception.getMessage()).contains(
-                    "Failed to parse layers trace. Found orphan layers "
-                            + "with parent layer id:1006 : 49");
-        }
-    }
-
-    // b/75276931
-    @Test
-    public void canDetectUncoveredRegion() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2308008331271L);
-
-        Assertions.Result result = entry.coversRegion(getDisplayBounds());
-
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("Region to test: Rect(0, 0 - 1440, 2880)");
-        assertThat(result.reason).contains("first empty point: 0, 99");
-        assertThat(result.reason).contains("visible regions:");
-        assertWithMessage("Reason contains list of visible regions")
-                .that(result.reason).contains("StatusBar#0Rect(0, 0 - 1440, 98");
-    }
-
-    // Visible region tests
-    @Test
-    public void canTestLayerVisibleRegion_layerDoesNotExist() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2308008331271L);
-
-        final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
-        Assertions.Result result = entry.hasVisibleRegion("ImaginaryLayer",
-                expectedVisibleRegion);
-
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("Could not find ImaginaryLayer");
-    }
-
-    @Test
-    public void canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2307993020072L);
-
-        final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
-        Assertions.Result result = entry.hasVisibleRegion("NexusLauncherActivity#2",
-                expectedVisibleRegion);
-
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains(
-                "Layer com.google.android.apps.nexuslauncher/com.google.android.apps"
-                        + ".nexuslauncher.NexusLauncherActivity#2 is invisible: activeBuffer=null"
-                        + " type != ColorLayer flags=1 (FLAG_HIDDEN set) visible region is empty");
-    }
-
-    @Test
-    public void canTestLayerVisibleRegion_layerIsHiddenByParent() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2308455948035L);
-
-        final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
-        Assertions.Result result = entry.hasVisibleRegion(
-                "SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main",
-                expectedVisibleRegion);
-
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains(
-                "Layer SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0 is "
-                        + "hidden by parent: com.android.chrome/com.google.android.apps.chrome"
-                        + ".Main#0");
-    }
-
-    @Test
-    public void canTestLayerVisibleRegion_incorrectRegionSize() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2308008331271L);
-
-        final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 99);
-        Assertions.Result result = entry.hasVisibleRegion(
-                "StatusBar",
-                expectedVisibleRegion);
-
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("StatusBar#0 has visible "
-                + "region:Rect(0, 0 - 1440, 98) expected:Rect(0, 0 - 1440, 99)");
-    }
-
-    @Test
-    public void canTestLayerVisibleRegion() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_emptyregion.pb");
-        LayersTrace.Entry entry = trace.getEntry(2308008331271L);
-
-        final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 98);
-        Assertions.Result result = entry.hasVisibleRegion("StatusBar", expectedVisibleRegion);
-
-        assertThat(result.passed()).isTrue();
-    }
-
-    @Test
-    public void canTestLayerVisibleRegion_layerIsNotVisible() {
-        LayersTrace trace = readLayerTraceFromFile(
-                "layers_trace_invalid_layer_visibility.pb");
-        LayersTrace.Entry entry = trace.getEntry(252794268378458L);
-
-        Assertions.Result result = entry.isVisible("com.android.server.wm.flicker.testapp");
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains(
-                "Layer com.android.server.wm.flicker.testapp/com.android.server.wm.flicker"
-                        + ".testapp.SimpleActivity#0 is invisible: type != ColorLayer visible "
-                        + "region is empty");
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java
deleted file mode 100644
index c46175c..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TestFileUtils.java
+++ /dev/null
@@ -1,36 +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 android.content.Context;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.google.common.io.ByteStreams;
-
-import java.io.InputStream;
-
-/**
- * Helper functions for test file resources.
- */
-class TestFileUtils {
-    static byte[] readTestFile(String relativePath) throws Exception {
-        Context context = InstrumentationRegistry.getContext();
-        InputStream in = context.getResources().getAssets().open("testdata/" + relativePath);
-        return ByteStreams.toByteArray(in);
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java
deleted file mode 100644
index 9c5e2059a..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/TransitionRunnerTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.os.Environment;
-
-import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
-import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
-import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
-import com.android.server.wm.flicker.monitor.ScreenRecorder;
-import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
-import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.InjectMocks;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.IOException;
-import java.nio.file.Paths;
-import java.util.List;
-
-/**
- * Contains {@link TransitionRunner} tests.
- * {@code atest FlickerLibTest:TransitionRunnerTest}
- */
-public class TransitionRunnerTest {
-    @Mock
-    private SimpleUiTransitions mTransitionsMock;
-    @Mock
-    private ScreenRecorder mScreenRecorderMock;
-    @Mock
-    private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock;
-    @Mock
-    private LayersTraceMonitor mLayersTraceMonitorMock;
-    @Mock
-    private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
-    @InjectMocks
-    private TransitionBuilder mTransitionBuilder;
-
-    @Before
-    public void init() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void transitionsRunInOrder() {
-        TransitionRunner.newBuilder()
-                .runBeforeAll(mTransitionsMock::turnOnDevice)
-                .runBefore(mTransitionsMock::openApp)
-                .run(mTransitionsMock::performMagic)
-                .runAfter(mTransitionsMock::closeApp)
-                .runAfterAll(mTransitionsMock::cleanUpTracks)
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .build()
-                .run();
-
-        InOrder orderVerifier = inOrder(mTransitionsMock);
-        orderVerifier.verify(mTransitionsMock).turnOnDevice();
-        orderVerifier.verify(mTransitionsMock).openApp();
-        orderVerifier.verify(mTransitionsMock).performMagic();
-        orderVerifier.verify(mTransitionsMock).closeApp();
-        orderVerifier.verify(mTransitionsMock).cleanUpTracks();
-    }
-
-    @Test
-    public void canCombineTransitions() {
-        TransitionRunner.newBuilder()
-                .runBeforeAll(mTransitionsMock::turnOnDevice)
-                .runBeforeAll(mTransitionsMock::turnOnDevice)
-                .runBefore(mTransitionsMock::openApp)
-                .runBefore(mTransitionsMock::openApp)
-                .run(mTransitionsMock::performMagic)
-                .run(mTransitionsMock::performMagic)
-                .runAfter(mTransitionsMock::closeApp)
-                .runAfter(mTransitionsMock::closeApp)
-                .runAfterAll(mTransitionsMock::cleanUpTracks)
-                .runAfterAll(mTransitionsMock::cleanUpTracks)
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .build()
-                .run();
-
-        final int wantedNumberOfInvocations = 2;
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).turnOnDevice();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).cleanUpTracks();
-    }
-
-    @Test
-    public void emptyTransitionPasses() {
-        List<TransitionResult> results = TransitionRunner.newBuilder()
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .build()
-                .run()
-                .getResults();
-        assertThat(results).hasSize(1);
-        assertThat(results.get(0).layersTraceExists()).isFalse();
-        assertThat(results.get(0).windowManagerTraceExists()).isFalse();
-        assertThat(results.get(0).screenCaptureVideoExists()).isFalse();
-    }
-
-    @Test
-    public void canRepeatTransitions() {
-        final int wantedNumberOfInvocations = 10;
-        TransitionRunner.newBuilder()
-                .runBeforeAll(mTransitionsMock::turnOnDevice)
-                .runBefore(mTransitionsMock::openApp)
-                .run(mTransitionsMock::performMagic)
-                .runAfter(mTransitionsMock::closeApp)
-                .runAfterAll(mTransitionsMock::cleanUpTracks)
-                .repeat(wantedNumberOfInvocations)
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .build()
-                .run();
-        verify(mTransitionsMock).turnOnDevice();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
-        verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
-        verify(mTransitionsMock).cleanUpTracks();
-    }
-
-    private void emptyTask() {
-
-    }
-
-    @Test
-    public void canCaptureWindowManagerTrace() {
-        mTransitionBuilder
-                .run(this::emptyTask)
-                .includeJankyRuns()
-                .skipLayersTrace()
-                .withTag("mCaptureWmTraceTransitionRunner")
-                .build().run();
-        InOrder orderVerifier = inOrder(mWindowManagerTraceMonitorMock);
-        orderVerifier.verify(mWindowManagerTraceMonitorMock).start();
-        orderVerifier.verify(mWindowManagerTraceMonitorMock).stop();
-        orderVerifier.verify(mWindowManagerTraceMonitorMock)
-                .save("mCaptureWmTraceTransitionRunner", 0);
-        verifyNoMoreInteractions(mWindowManagerTraceMonitorMock);
-    }
-
-    @Test
-    public void canCaptureLayersTrace() {
-        mTransitionBuilder
-                .run(this::emptyTask)
-                .includeJankyRuns()
-                .skipWindowManagerTrace()
-                .withTag("mCaptureLayersTraceTransitionRunner")
-                .build().run();
-        InOrder orderVerifier = inOrder(mLayersTraceMonitorMock);
-        orderVerifier.verify(mLayersTraceMonitorMock).start();
-        orderVerifier.verify(mLayersTraceMonitorMock).stop();
-        orderVerifier.verify(mLayersTraceMonitorMock)
-                .save("mCaptureLayersTraceTransitionRunner", 0);
-        verifyNoMoreInteractions(mLayersTraceMonitorMock);
-    }
-
-    @Test
-    public void canRecordEachRun() throws IOException {
-        mTransitionBuilder
-                .run(this::emptyTask)
-                .withTag("mRecordEachRun")
-                .recordEachRun()
-                .includeJankyRuns()
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .repeat(2)
-                .build().run();
-        InOrder orderVerifier = inOrder(mScreenRecorderMock);
-        orderVerifier.verify(mScreenRecorderMock).start();
-        orderVerifier.verify(mScreenRecorderMock).stop();
-        orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 0);
-        orderVerifier.verify(mScreenRecorderMock).start();
-        orderVerifier.verify(mScreenRecorderMock).stop();
-        orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 1);
-        verifyNoMoreInteractions(mScreenRecorderMock);
-    }
-
-    @Test
-    public void canRecordAllRuns() throws IOException {
-        doReturn(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath(),
-                "mRecordAllRuns.mp4")).when(mScreenRecorderMock).save("mRecordAllRuns");
-        mTransitionBuilder
-                .run(this::emptyTask)
-                .recordAllRuns()
-                .includeJankyRuns()
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .withTag("mRecordAllRuns")
-                .repeat(2)
-                .build().run();
-        InOrder orderVerifier = inOrder(mScreenRecorderMock);
-        orderVerifier.verify(mScreenRecorderMock).start();
-        orderVerifier.verify(mScreenRecorderMock).stop();
-        orderVerifier.verify(mScreenRecorderMock).save("mRecordAllRuns");
-        verifyNoMoreInteractions(mScreenRecorderMock);
-    }
-
-    @Test
-    public void canSkipJankyRuns() {
-        doReturn(false).doReturn(true).doReturn(false)
-                .when(mWindowAnimationFrameStatsMonitor).jankyFramesDetected();
-        List<TransitionResult> results = mTransitionBuilder
-                .run(this::emptyTask)
-                .skipLayersTrace()
-                .skipWindowManagerTrace()
-                .repeat(3)
-                .build().run().getResults();
-        assertThat(results).hasSize(2);
-    }
-
-    public static class SimpleUiTransitions {
-        public void turnOnDevice() {
-        }
-
-        public void openApp() {
-        }
-
-        public void performMagic() {
-        }
-
-        public void closeApp() {
-        }
-
-        public void cleanUpTracks() {
-        }
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java
deleted file mode 100644
index 4927871..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WindowManagerTraceTest.java
+++ /dev/null
@@ -1,108 +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 com.android.server.wm.flicker.TestFileUtils.readTestFile;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.server.wm.flicker.Assertions.Result;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Contains {@link WindowManagerTrace} tests.
- * To run this test: {@code atest FlickerLibTest:WindowManagerTraceTest}
- */
-public class WindowManagerTraceTest {
-    private WindowManagerTrace mTrace;
-
-    private static WindowManagerTrace readWindowManagerTraceFromFile(String relativePath) {
-        try {
-            return WindowManagerTrace.parseFrom(readTestFile(relativePath));
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Before
-    public void setup() {
-        mTrace = readWindowManagerTraceFromFile("wm_trace_openchrome.pb");
-    }
-
-    @Test
-    public void canParseAllEntries() {
-        assertThat(mTrace.getEntries().get(0).getTimestamp()).isEqualTo(241777211939236L);
-        assertThat(mTrace.getEntries().get(mTrace.getEntries().size() - 1).getTimestamp()).isEqualTo
-                (241779809471942L);
-    }
-
-    @Test
-    public void canDetectAboveAppWindowVisibility() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
-        Result result = entry.isAboveAppWindowVisible("NavigationBar");
-        assertThat(result.passed()).isTrue();
-    }
-
-    @Test
-    public void canDetectBelowAppWindowVisibility() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
-        Result result = entry.isBelowAppWindowVisible("wallpaper");
-        assertThat(result.passed()).isTrue();
-    }
-
-    @Test
-    public void canDetectAppWindowVisibility() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
-        Result result = entry.isAppWindowVisible("com.google.android.apps.nexuslauncher");
-        assertThat(result.passed()).isTrue();
-    }
-
-    @Test
-    public void canFailWithReasonForVisibilityChecks_windowNotFound() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
-        Result result = entry.isAboveAppWindowVisible("ImaginaryWindow");
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("ImaginaryWindow cannot be found");
-    }
-
-    @Test
-    public void canFailWithReasonForVisibilityChecks_windowNotVisible() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
-        Result result = entry.isAboveAppWindowVisible("AssistPreviewPanel");
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("AssistPreviewPanel is invisible");
-    }
-
-    @Test
-    public void canDetectAppZOrder() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
-        Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.chrome");
-        assertThat(result.passed()).isTrue();
-    }
-
-    @Test
-    public void canFailWithReasonForZOrderChecks_windowNotOnTop() {
-        WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
-        Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.nexuslauncher");
-        assertThat(result.failed()).isTrue();
-        assertThat(result.reason).contains("wanted=com.google.android.apps.nexuslauncher");
-        assertThat(result.reason).contains("found=com.android.chrome/"
-                + "com.google.android.apps.chrome.Main");
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java
deleted file mode 100644
index d547a18..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/WmTraceSubjectTest.java
+++ /dev/null
@@ -1,49 +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 com.android.server.wm.flicker.TestFileUtils.readTestFile;
-import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
-
-import org.junit.Test;
-
-/**
- * Contains {@link WmTraceSubject} tests.
- * To run this test: {@code atest FlickerLibTest:WmTraceSubjectTest}
- */
-public class WmTraceSubjectTest {
-    private static WindowManagerTrace readWmTraceFromFile(String relativePath) {
-        try {
-            return WindowManagerTrace.parseFrom(readTestFile(relativePath));
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Test
-    public void testCanTransitionInAppWindow() {
-        WindowManagerTrace trace = readWmTraceFromFile("wm_trace_openchrome2.pb");
-
-        assertThat(trace).showsAppWindowOnTop("com.google.android.apps.nexuslauncher/"
-                + ".NexusLauncherActivity").forRange(174684850717208L, 174685957511016L);
-        assertThat(trace).showsAppWindowOnTop(
-                "com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
-                .then()
-                .showsAppWindowOnTop("com.android.chrome")
-                .forAllEntries();
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java
deleted file mode 100644
index dbd6761..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/LayersTraceMonitorTest.java
+++ /dev/null
@@ -1,78 +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.monitor;
-
-import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_H;
-import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_L;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
-
-import com.google.common.io.Files;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * Contains {@link LayersTraceMonitor} tests.
- * To run this test: {@code atest FlickerLibTest:LayersTraceMonitorTest}
- */
-public class LayersTraceMonitorTest {
-    private LayersTraceMonitor mLayersTraceMonitor;
-
-    @Before
-    public void setup() {
-        mLayersTraceMonitor = new LayersTraceMonitor();
-    }
-
-    @After
-    public void teardown() {
-        mLayersTraceMonitor.stop();
-        mLayersTraceMonitor.getOutputTraceFilePath("captureLayersTrace").toFile().delete();
-    }
-
-    @Test
-    public void canStartLayersTrace() throws Exception {
-        mLayersTraceMonitor.start();
-        assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
-    }
-
-    @Test
-    public void canStopLayersTrace() throws Exception {
-        mLayersTraceMonitor.start();
-        assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
-        mLayersTraceMonitor.stop();
-        assertThat(mLayersTraceMonitor.isEnabled()).isFalse();
-    }
-
-    @Test
-    public void captureLayersTrace() throws Exception {
-        mLayersTraceMonitor.start();
-        mLayersTraceMonitor.stop();
-        File testFile = mLayersTraceMonitor.save("captureLayersTrace").toFile();
-        assertThat(testFile.exists()).isTrue();
-        byte[] trace = Files.toByteArray(testFile);
-        assertThat(trace.length).isGreaterThan(0);
-        LayersTraceFileProto mLayerTraceFileProto = LayersTraceFileProto.parseFrom(trace);
-        assertThat(mLayerTraceFileProto.magicNumber).isEqualTo(
-                (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java
deleted file mode 100644
index e73eecc..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/ScreenRecorderTest.java
+++ /dev/null
@@ -1,70 +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.monitor;
-
-import static android.os.SystemClock.sleep;
-
-import static com.android.server.wm.flicker.monitor.ScreenRecorder.DEFAULT_OUTPUT_PATH;
-import static com.android.server.wm.flicker.monitor.ScreenRecorder.getPath;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Contains {@link ScreenRecorder} tests.
- * To run this test: {@code atest FlickerLibTest:ScreenRecorderTest}
- */
-public class ScreenRecorderTest {
-    private static final String TEST_VIDEO_FILENAME = "test.mp4";
-    private ScreenRecorder mScreenRecorder;
-
-    @Before
-    public void setup() {
-        mScreenRecorder = new ScreenRecorder();
-    }
-
-    @After
-    public void teardown() {
-        DEFAULT_OUTPUT_PATH.toFile().delete();
-        getPath(TEST_VIDEO_FILENAME).toFile().delete();
-    }
-
-    @Test
-    public void videoIsRecorded() {
-        mScreenRecorder.start();
-        sleep(100);
-        mScreenRecorder.stop();
-        File file = DEFAULT_OUTPUT_PATH.toFile();
-        assertThat(file.exists()).isTrue();
-    }
-
-    @Test
-    public void videoCanBeSaved() {
-        mScreenRecorder.start();
-        sleep(100);
-        mScreenRecorder.stop();
-        mScreenRecorder.save(TEST_VIDEO_FILENAME);
-        File file = getPath(TEST_VIDEO_FILENAME).toFile();
-        assertThat(file.exists()).isTrue();
-    }
-}
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
deleted file mode 100644
index dd6fed0..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
+++ /dev/null
@@ -1,50 +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.monitor;
-
-import static com.android.server.wm.flicker.AutomationUtils.wakeUpAndGoToHomeScreen;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Contains {@link WindowAnimationFrameStatsMonitor} tests.
- * To run this test: {@code atest FlickerLibTest:WindowAnimationFrameStatsMonitorTest}
- */
-public class WindowAnimationFrameStatsMonitorTest {
-    private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
-
-    @Before
-    public void setup() {
-        mWindowAnimationFrameStatsMonitor = new WindowAnimationFrameStatsMonitor(
-                InstrumentationRegistry.getInstrumentation());
-        wakeUpAndGoToHomeScreen();
-    }
-
-    // TODO(vishnun)
-    @Ignore("Disabled until app-helper libraries are available.")
-    @Test
-    public void captureWindowAnimationFrameStats() throws Exception {
-        mWindowAnimationFrameStatsMonitor.start();
-        //AppHelperWrapper.getInstance().getHelper(CHROME).open();
-        //AppHelperWrapper.getInstance().getHelper(CHROME).exit();
-        mWindowAnimationFrameStatsMonitor.stop();
-    }
-}
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java
deleted file mode 100644
index 56284d7..0000000
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitorTest.java
+++ /dev/null
@@ -1,79 +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.monitor;
-
-import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.android.server.wm.nano.WindowManagerTraceFileProto;
-
-import com.google.common.io.Files;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * Contains {@link WindowManagerTraceMonitor} tests.
- * To run this test: {@code atest FlickerLibTest:WindowManagerTraceMonitorTest}
- */
-public class WindowManagerTraceMonitorTest {
-    private WindowManagerTraceMonitor mWindowManagerTraceMonitor;
-
-    @Before
-    public void setup() {
-        mWindowManagerTraceMonitor = new WindowManagerTraceMonitor();
-    }
-
-    @After
-    public void teardown() {
-        mWindowManagerTraceMonitor.stop();
-        mWindowManagerTraceMonitor.getOutputTraceFilePath("captureWindowTrace").toFile().delete();
-    }
-
-    @Test
-    public void canStartWindowTrace() throws Exception {
-        mWindowManagerTraceMonitor.start();
-        assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
-    }
-
-    @Test
-    public void canStopWindowTrace() throws Exception {
-        mWindowManagerTraceMonitor.start();
-        assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
-        mWindowManagerTraceMonitor.stop();
-        assertThat(mWindowManagerTraceMonitor.isEnabled()).isFalse();
-    }
-
-    @Test
-    public void captureWindowTrace() throws Exception {
-        mWindowManagerTraceMonitor.start();
-        mWindowManagerTraceMonitor.stop();
-        File testFile = mWindowManagerTraceMonitor.save("captureWindowTrace").toFile();
-        assertThat(testFile.exists()).isTrue();
-        byte[] trace = Files.toByteArray(testFile);
-        assertThat(trace.length).isGreaterThan(0);
-        WindowManagerTraceFileProto mWindowTraceFileProto = WindowManagerTraceFileProto.parseFrom(
-                trace);
-        assertThat(mWindowTraceFileProto.magicNumber).isEqualTo(
-                (long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
-    }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java
index b6860cb..aa591d9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ChangeAppRotationTest.java
@@ -29,11 +29,15 @@
 import android.view.Surface;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
@@ -44,18 +48,19 @@
  * Cycle through supported app rotations.
  * To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
  */
-@RunWith(Parameterized.class)
 @LargeTest
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class ChangeAppRotationTest extends FlickerTestBase {
-    private int beginRotation;
-    private int endRotation;
+    private int mBeginRotation;
+    private int mEndRotation;
 
     public ChangeAppRotationTest(String beginRotationName, String endRotationName,
             int beginRotation, int endRotation) {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
-        this.beginRotation = beginRotation;
-        this.endRotation = endRotation;
+        this.mBeginRotation = beginRotation;
+        this.mEndRotation = endRotation;
     }
 
     @Parameters(name = "{0}-{1}")
@@ -77,15 +82,19 @@
     @Before
     public void runTransition() {
         super.runTransition(
-                changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build());
+                changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation).build());
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkVisibility_navBarWindowIsAlwaysVisible() {
         checkResults(result -> assertThat(result)
                 .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkVisibility_statusBarWindowIsAlwaysVisible() {
         checkResults(result -> assertThat(result)
@@ -94,8 +103,8 @@
 
     @Test
     public void checkPosition_navBarLayerRotatesAndScales() {
-        Rect startingPos = getNavigationBarPosition(beginRotation);
-        Rect endingPos = getNavigationBarPosition(endRotation);
+        Rect startingPos = getNavigationBarPosition(mBeginRotation);
+        Rect endingPos = getNavigationBarPosition(mEndRotation);
         checkResults(result -> {
                     LayersTraceSubject.assertThat(result)
                             .hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
@@ -108,22 +117,22 @@
 
     @Test
     public void checkPosition_appLayerRotates() {
-        Rect startingPos = getAppPosition(beginRotation);
-        Rect endingPos = getAppPosition(endRotation);
+        Rect startingPos = getAppPosition(mBeginRotation);
+        Rect endingPos = getAppPosition(mEndRotation);
         Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
         checkResults(result -> {
                     LayersTraceSubject.assertThat(result)
-                            .hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning();
+                            .hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning();
                     LayersTraceSubject.assertThat(result)
-                            .hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd();
+                            .hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd();
                 }
         );
     }
 
     @Test
     public void checkPosition_statusBarLayerScales() {
-        Rect startingPos = getStatusBarPosition(beginRotation);
-        Rect endingPos = getStatusBarPosition(endRotation);
+        Rect startingPos = getStatusBarPosition(mBeginRotation);
+        Rect endingPos = getStatusBarPosition(mEndRotation);
         checkResults(result -> {
                     LayersTraceSubject.assertThat(result)
                             .hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
@@ -134,12 +143,16 @@
         );
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkVisibility_navBarLayerIsAlwaysVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
                 .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkVisibility_statusBarLayerIsAlwaysVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
index 6590b86..9deb977 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
@@ -26,8 +26,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test IME window closing back to app window transitions.
@@ -35,6 +37,7 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class CloseImeWindowToAppTest extends FlickerTestBase {
 
     private static final String IME_WINDOW_TITLE = "InputMethod";
@@ -44,7 +47,7 @@
 
     @Before
     public void runTransition() {
-        super.runTransition(editTextLoseFocusToApp(uiDevice)
+        super.runTransition(editTextLoseFocusToApp(mUiDevice)
                 .includeJankyRuns().build());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
index 4771b02..cce5a2a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
@@ -26,8 +26,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test IME window closing to home transitions.
@@ -35,6 +37,7 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class CloseImeWindowToHomeTest extends FlickerTestBase {
 
     private static final String IME_WINDOW_TITLE = "InputMethod";
@@ -44,7 +47,7 @@
 
     @Before
     public void runTransition() {
-        super.runTransition(editTextLoseFocusToHome(uiDevice)
+        super.runTransition(editTextLoseFocusToHome(mUiDevice)
                 .includeJankyRuns().build());
     }
 
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..1d44ea4 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.
@@ -66,7 +67,7 @@
                     device.setOrientationNatural();
             }
             // Wait for animation to complete
-            sleep(3000);
+            sleep(1000);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
@@ -215,10 +216,10 @@
 
     static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
             UiDevice device, Rational startRatio, Rational stopRatio) {
-        String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" +
-                testAppBottom.getLauncherName() + "_" +
-                startRatio.toString().replace("/", ":") + "_to_" +
-                stopRatio.toString().replace("/", ":");
+        String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
+                + testAppBottom.getLauncherName() + "_"
+                + startRatio.toString().replace("/", ":") + "_to_"
+                + stopRatio.toString().replace("/", ":");
         return TransitionRunner.newBuilder()
                 .withTag(testTag)
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
@@ -230,7 +231,7 @@
                 .runBefore(() -> launchSplitScreen(device))
                 .runBefore(() -> {
                     UiObject2 snapshot = device.findObject(
-                            By.res("com.google.android.apps.nexuslauncher", "snapshot"));
+                            By.res(device.getLauncherPackageName(), "snapshot"));
                     snapshot.click();
                 })
                 .runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
@@ -315,4 +316,4 @@
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
index 61cca0d..9836655 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
@@ -22,17 +22,22 @@
 import android.view.Surface;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.FixMethodOrder;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Tests to help debug individual transitions, capture video recordings and create test cases.
  */
+@LargeTest
 @Ignore("Used for debugging transitions used in FlickerTests.")
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class DebugTest {
     private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
             "com.android.server.wm.flicker.testapp", "SimpleApp");
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..6e8e0c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
@@ -16,20 +16,23 @@
 
 package com.android.server.wm.flicker;
 
-import static com.android.server.wm.flicker.AutomationUtils.setDefaultWait;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.os.Bundle;
 import android.platform.helpers.IAppHelper;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Before;
 
 import java.util.HashMap;
 import java.util.List;
@@ -51,10 +54,16 @@
     static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
     private static HashMap<String, List<TransitionResult>> transitionResults =
             new HashMap<>();
-    IAppHelper testApp;
-    UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-    private List<TransitionResult> results;
-    private TransitionResult lastResult = null;
+    IAppHelper mTestApp;
+    UiDevice mUiDevice;
+    private List<TransitionResult> mResults;
+    private TransitionResult mLastResult = null;
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
 
     /**
      * Teardown any system settings and clean up test artifacts from the file system.
@@ -91,14 +100,14 @@
      */
     void runTransition(TransitionRunner transition) {
         if (transitionResults.containsKey(transition.getTestTag())) {
-            results = transitionResults.get(transition.getTestTag());
+            mResults = transitionResults.get(transition.getTestTag());
             return;
         }
-        results = transition.run().getResults();
+        mResults = transition.run().getResults();
         /* Fail if we don't have any results due to jank */
         assertWithMessage("No results to test because all transition runs were invalid because "
-                + "of Jank").that(results).isNotEmpty();
-        transitionResults.put(transition.getTestTag(), results);
+                + "of Jank").that(mResults).isNotEmpty();
+        transitionResults.put(transition.getTestTag(), mResults);
     }
 
     /**
@@ -106,11 +115,11 @@
      */
     void checkResults(Consumer<TransitionResult> assertion) {
 
-        for (TransitionResult result : results) {
-            lastResult = result;
+        for (TransitionResult result : mResults) {
+            mLastResult = result;
             assertion.accept(result);
         }
-        lastResult = null;
+        mLastResult = null;
     }
 
     /**
@@ -119,8 +128,8 @@
      */
     @After
     public void markArtifactsForSaving() {
-        if (lastResult != null) {
-            lastResult.flagForSaving();
+        if (mLastResult != null) {
+            mLastResult.flagForSaving();
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
index 7818c4e..8d99054 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
@@ -21,12 +21,16 @@
 import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test cold launch app from launcher.
@@ -34,16 +38,17 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class OpenAppColdTest extends FlickerTestBase {
 
     public OpenAppColdTest() {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(getOpenAppCold(testApp, uiDevice).build());
+        super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
     }
 
     @Test
@@ -61,9 +66,9 @@
     @Test
     public void checkVisibility_wallpaperWindowBecomesInvisible() {
         checkResults(result -> assertThat(result)
-                .showsBelowAppWindow("wallpaper")
+                .showsBelowAppWindow("Wallpaper")
                 .then()
-                .hidesBelowAppWindow("wallpaper")
+                .hidesBelowAppWindow("Wallpaper")
                 .forAllEntries());
     }
 
@@ -71,13 +76,15 @@
     public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
         checkResults(result -> assertThat(result)
                 .showsAppWindowOnTop(
-                        "com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
+                        "com.android.launcher3/.Launcher")
                 .then()
-                .showsAppWindowOnTop(testApp.getPackage())
+                .showsAppWindowOnTop(mTestApp.getPackage())
                 .forAllEntries());
     }
 
     @Test
+    @FlakyTest(bugId = 141235985)
+    @Ignore("Waiting bug feedback")
     public void checkCoveredRegion_noUncoveredRegions() {
         checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
                 getDisplayBounds()).forAllEntries());
@@ -98,9 +105,9 @@
     @Test
     public void checkVisibility_wallpaperLayerBecomesInvisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer("wallpaper")
+                .showsLayer("Wallpaper")
                 .then()
-                .hidesLayer("wallpaper")
+                .hidesLayer("Wallpaper")
                 .forAllEntries());
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java
index 63018ec..f8b7938 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppToSplitScreenTest.java
@@ -24,8 +24,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test open app to split screen.
@@ -33,16 +35,17 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class OpenAppToSplitScreenTest extends FlickerTestBase {
 
     public OpenAppToSplitScreenTest() {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build());
+        super.runTransition(appToSplitScreen(mTestApp, mUiDevice).includeJankyRuns().build());
     }
 
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
index 1aba930..e8702c2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
@@ -21,12 +21,16 @@
 import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test warm launch app.
@@ -34,16 +38,17 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class OpenAppWarmTest extends FlickerTestBase {
 
     public OpenAppWarmTest() {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(openAppWarm(testApp, uiDevice).build());
+        super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
     }
 
     @Test
@@ -61,9 +66,9 @@
     @Test
     public void checkVisibility_wallpaperBecomesInvisible() {
         checkResults(result -> assertThat(result)
-                .showsBelowAppWindow("wallpaper")
+                .showsBelowAppWindow("Wallpaper")
                 .then()
-                .hidesBelowAppWindow("wallpaper")
+                .hidesBelowAppWindow("Wallpaper")
                 .forAllEntries());
     }
 
@@ -71,12 +76,14 @@
     public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
         checkResults(result -> assertThat(result)
                 .showsAppWindowOnTop(
-                        "com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
+                        "com.android.launcher3/.Launcher")
                 .then()
-                .showsAppWindowOnTop(testApp.getPackage())
+                .showsAppWindowOnTop(mTestApp.getPackage())
                 .forAllEntries());
     }
 
+    @FlakyTest(bugId = 141235985)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkCoveredRegion_noUncoveredRegions() {
         checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
@@ -98,9 +105,9 @@
     @Test
     public void checkVisibility_wallpaperLayerBecomesInvisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer("wallpaper")
+                .showsLayer("Wallpaper")
                 .then()
-                .hidesLayer("wallpaper")
+                .hidesLayer("Wallpaper")
                 .forAllEntries());
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
index a81fa8e..9f5cfce 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
@@ -23,8 +23,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test IME window opening transitions.
@@ -32,13 +34,14 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class OpenImeWindowTest extends FlickerTestBase {
 
     private static final String IME_WINDOW_TITLE = "InputMethod";
 
     @Before
     public void runTransition() {
-        super.runTransition(editTextSetFocus(uiDevice)
+        super.runTransition(editTextSetFocus(mUiDevice)
                 .includeJankyRuns().build());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
index 50dba81..1031baf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
@@ -28,12 +28,16 @@
 import android.util.Rational;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test split screen resizing window transitions.
@@ -41,10 +45,13 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 140856143)
+@Ignore("Waiting bug feedback")
 public class ResizeSplitScreenTest extends FlickerTestBase {
 
     public ResizeSplitScreenTest() {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
@@ -53,7 +60,7 @@
         IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
                 .getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "ImeApp");
-        super.runTransition(resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3),
+        super.runTransition(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, new Rational(1, 3),
                 new Rational(2, 3)).includeJankyRuns().build());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java
index 117ac5a..ae55a75 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SeamlessAppRotationTest.java
@@ -33,8 +33,10 @@
 import androidx.test.filters.LargeTest;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
@@ -47,6 +49,7 @@
  */
 @LargeTest
 @RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
 public class SeamlessAppRotationTest extends FlickerTestBase {
     private int mBeginRotation;
     private int mEndRotation;
@@ -105,7 +108,7 @@
 
         super.runTransition(
                 changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
-                        uiDevice, mBeginRotation, mEndRotation).repeat(5).build());
+                        mUiDevice, mBeginRotation, mEndRotation).repeat(5).build());
     }
 
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java
index 1d30df9..85a1494 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/SplitScreenToLauncherTest.java
@@ -25,8 +25,11 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
  * Test open app to split screen.
@@ -34,16 +37,19 @@
  */
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 140856143)
+@Ignore("Waiting bug feedback")
 public class SplitScreenToLauncherTest extends FlickerTestBase {
 
     public SplitScreenToLauncherTest() {
-        this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
+        this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build());
+        super.runTransition(splitScreenToLauncher(mTestApp, mUiDevice).includeJankyRuns().build());
     }
 
     @Test
@@ -62,13 +68,12 @@
                 .forAllEntries());
     }
 
-    @FlakyTest(bugId = 79686616)
     @Test
     public void checkVisibility_appLayerBecomesInVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(testApp.getPackage())
+                .showsLayer(mTestApp.getPackage())
                 .then()
-                .hidesLayer(testApp.getPackage())
+                .hidesLayer(mTestApp.getPackage())
                 .forAllEntries());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java
deleted file mode 100644
index 79a0220..0000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/StandardAppHelper.java
+++ /dev/null
@@ -1,59 +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 android.app.Instrumentation;
-import android.platform.helpers.AbstractStandardAppHelper;
-
-/**
- * Class to take advantage of {@code IAppHelper} interface so the same test can be run against
- * first party and third party apps.
- */
-public class StandardAppHelper extends AbstractStandardAppHelper {
-    private final String mPackageName;
-    private final String mLauncherName;
-
-    public StandardAppHelper(Instrumentation instr, String packageName, String launcherName) {
-        super(instr);
-        mPackageName = packageName;
-        mLauncherName = launcherName;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String getPackage() {
-        return mPackageName;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String getLauncherName() {
-        return mLauncherName;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void dismissInitialDialogs() {
-
-    }
-}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
index 3a0c1c9..5cf81cb 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
@@ -17,7 +17,6 @@
 package com.android.server.wm.flicker.testapp;
 
 import static android.os.SystemClock.sleep;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 
 import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
 
@@ -39,8 +38,8 @@
         super.onCreate(savedInstanceState);
         enableSeamlessRotation();
         setContentView(R.layout.activity_simple);
-        boolean starveUiThread = getIntent().getExtras() != null &&
-                getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
+        boolean starveUiThread = getIntent().getExtras() != null
+                && getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
         if (starveUiThread) {
             starveUiThread();
         }
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 9a60330..06b58fd 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -626,11 +626,40 @@
         // Verify that health check is failed
         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.
+        // Clear failed packages and forward time to expire the observation duration
         observer.mMitigatedPackages.clear();
-        watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+        moveTimeForwardAndDispatch(LONG_DURATION);
+
+        // Verify that health check failure is not notified again
+        assertThat(observer.mMitigatedPackages).isEmpty();
+    }
+
+    /**
+     * Tests failure when health check duration is different from package observation duration
+     * Failure is also notified only once.
+     */
+    @Test
+    public void testExplicitHealthCheckFailureAfterExpiry() {
+        TestController controller = new TestController();
+        PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1,
+                PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+
+        // Start observing with explicit health checks for APP_A and
+        // package observation duration == SHORT_DURATION / 2
+        // health check duration == SHORT_DURATION (set by default in the TestController)
+        controller.setSupportedPackages(Arrays.asList(APP_A));
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+
+        // Forward time to expire the observation duration
+        moveTimeForwardAndDispatch(SHORT_DURATION / 2);
+
+        // Verify that health check is failed
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
+
+        // Clear failed packages and forward time to expire the health check duration
+        observer.mMitigatedPackages.clear();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify that health check failure is not notified again
         assertThat(observer.mMitigatedPackages).isEmpty();
@@ -702,6 +731,141 @@
         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) {
         InstrumentationRegistry
                 .getInstrumentation()
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index f2f258a..9e21db7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -504,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
@@ -4315,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
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/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/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/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
index a86c226..d1a86c2 100644
--- a/tools/protologtool/Android.bp
+++ b/tools/protologtool/Android.bp
@@ -1,13 +1,21 @@
+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",
-    srcs: [
-        "src/**/*.kt",
-    ],
     static_libs: [
-        "javaparser",
-        "windowmanager-log-proto",
-        "jsonlib",
+        "protologtool-lib",
     ],
 }
 
@@ -15,13 +23,10 @@
     name: "protologtool-tests",
     test_suites: ["general-tests"],
     srcs: [
-        "src/**/*.kt",
         "tests/**/*.kt",
     ],
     static_libs: [
-        "javaparser",
-        "windowmanager-log-proto",
-        "jsonlib",
+        "protologtool-lib",
         "junit",
         "mockito",
     ],
diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt
index f5e53c4..cabebd5 100644
--- a/tools/protologtool/manifest.txt
+++ b/tools/protologtool/manifest.txt
@@ -1 +1 @@
-Main-class: com.android.protologtool.ProtoLogTool
+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..cb29508
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.MethodCallExpr
+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(position: String, messageString: String, logLevel: LogLevel, logGroup: LogGroup): Int {
+        return (position + messageString + logLevel.name + logGroup.name)
+                .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)
+        }
+    }
+
+    fun getPositionString(call: MethodCallExpr, fileName: String): String {
+        return when {
+            call.range.isPresent -> "$fileName:${call.range.get().begin.line}"
+            else -> fileName
+        }
+    }
+}
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..aa3e00f
--- /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_ENABLED_METHOD = "isEnabled"
+        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..53834a6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -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.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 pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
+                    .get().nameAsString else ""
+            val newPath = pack.replace('.', '/') + '/' + file.name
+            val outSrc = when {
+                containsProtoLogText(text, command.protoLogClassNameArg) ->
+                    transformer.processClass(text, newPath, code)
+                else -> text
+            }
+            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)) {
+                val code = StaticJavaParser.parse(text)
+                val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
+                        .get().nameAsString else ""
+                val newPath = pack.replace('.', '/') + '/' + file.name
+                builder.processClass(code, newPath)
+            }
+        }
+        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..3f38bc0
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -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.protolog.tool
+
+import com.android.protolog.tool.Constants.IS_ENABLED_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 position = CodeUtils.getPositionString(call, fileName)
+            val hash = CodeUtils.hash(position, messageString, level, group)
+            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.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg)
+            newCall.setScope(protoLogImplClassNode)
+            // Create a call to ProtoLogImpl.isEnabled(GROUP)
+            // Out: com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)
+            val isLogEnabled = MethodCallExpr(protoLogImplClassNode, IS_ENABLED_METHOD,
+                NodeList<Expression>(newCall.arguments[0].clone()))
+            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 (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) {
+            //          long protoLogParam0 = arg;
+            //          com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0);
+            //      }
+            ifStmt = IfStmt(isLogEnabled, 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)
+    private var fileName: String = ""
+
+    fun processClass(
+        code: String,
+        path: String,
+        compilationUnit: CompilationUnit =
+               StaticJavaParser.parse(code)
+    ): String {
+        fileName = path
+        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..4c41797
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -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.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 position = CodeUtils.getPositionString(call, fileName)
+            val key = CodeUtils.hash(position, messageString, level, group)
+            if (statements.containsKey(key)) {
+                if (statements[key] != LogCall(messageString, level, group, position)) {
+                    throw HashCollisionException(
+                            "Please modify the log message \"$messageString\" " +
+                                    "or \"${statements[key]}\" - their hashes are equal.")
+                }
+            } else {
+                groups.add(group)
+                statements[key] = LogCall(messageString, level, group, position)
+                call.range.isPresent
+            }
+        }
+    }
+
+    private val statements: MutableMap<Int, LogCall> = mutableMapOf()
+    private val groups: MutableSet<LogGroup> = mutableSetOf()
+    private var fileName: String = ""
+
+    fun processClass(unit: CompilationUnit, fileName: String) {
+        this.fileName = fileName
+        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.messageString)
+            writer.name("level")
+            writer.value(value.logLevel.name)
+            writer.name("group")
+            writer.value(value.logGroup.name)
+            writer.name("at")
+            writer.value(value.position)
+            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()
+    }
+
+    data class LogCall(
+        val messageString: String,
+        val logLevel: LogLevel,
+        val logGroup: LogGroup,
+        val position: String
+    )
+}
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/src/com/android/protologtool/CodeUtils.kt b/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
deleted file mode 100644
index facca62..0000000
--- a/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
+++ /dev/null
@@ -1,135 +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.protologtool
-
-import com.github.javaparser.StaticJavaParser
-import com.github.javaparser.ast.CompilationUnit
-import com.github.javaparser.ast.ImportDeclaration
-import com.github.javaparser.ast.NodeList
-import com.github.javaparser.ast.expr.BinaryExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.MethodCallExpr
-import com.github.javaparser.ast.expr.SimpleName
-import com.github.javaparser.ast.expr.StringLiteralExpr
-import com.github.javaparser.ast.expr.TypeExpr
-import com.github.javaparser.ast.type.PrimitiveType
-import com.github.javaparser.ast.type.Type
-
-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)
-        }
-    }
-
-    enum class LogDataTypes(
-        val type: Type,
-        val toType: (Expression) -> Expression = { expr -> expr }
-    ) {
-        // When adding new LogDataType make sure to update {@code logDataTypesToBitMask} accordingly
-        STRING(StaticJavaParser.parseClassOrInterfaceType("String"),
-                { expr ->
-                    MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")),
-                            SimpleName("valueOf"), NodeList(expr))
-                }),
-        LONG(PrimitiveType.longType()),
-        DOUBLE(PrimitiveType.doubleType()),
-        BOOLEAN(PrimitiveType.booleanType());
-    }
-
-    fun parseFormatString(messageString: String): List<LogDataTypes> {
-        val types = mutableListOf<LogDataTypes>()
-        var i = 0
-        while (i < messageString.length) {
-            if (messageString[i] == '%') {
-                if (i + 1 >= messageString.length) {
-                    throw InvalidFormatStringException("Invalid format string in config")
-                }
-                when (messageString[i + 1]) {
-                    'b' -> types.add(CodeUtils.LogDataTypes.BOOLEAN)
-                    'd', 'o', 'x' -> types.add(CodeUtils.LogDataTypes.LONG)
-                    'f', 'e', 'g' -> types.add(CodeUtils.LogDataTypes.DOUBLE)
-                    's' -> types.add(CodeUtils.LogDataTypes.STRING)
-                    '%' -> {
-                    }
-                    else -> throw InvalidFormatStringException("Invalid format string field" +
-                            " %${messageString[i + 1]}")
-                }
-                i += 2
-            } else {
-                i += 1
-            }
-        }
-        return types
-    }
-
-    fun logDataTypesToBitMask(types: List<LogDataTypes>): Int {
-        if (types.size > 16) {
-            throw InvalidFormatStringException("Too many log call parameters " +
-                    "- max 16 parameters supported")
-        }
-        var mask = 0
-        types.forEachIndexed { idx, type ->
-            val x = LogDataTypes.values().indexOf(type)
-            mask = mask or (x shl (idx * 2))
-        }
-        return mask
-    }
-}
diff --git a/tools/protologtool/src/com/android/protologtool/CommandOptions.kt b/tools/protologtool/src/com/android/protologtool/CommandOptions.kt
deleted file mode 100644
index df49e15..0000000
--- a/tools/protologtool/src/com/android/protologtool/CommandOptions.kt
+++ /dev/null
@@ -1,205 +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.protologtool
-
-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/protologtool/Constants.kt b/tools/protologtool/src/com/android/protologtool/Constants.kt
deleted file mode 100644
index 2ccfc4d..0000000
--- a/tools/protologtool/src/com/android/protologtool/Constants.kt
+++ /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.protologtool
-
-object Constants {
-        const val NAME = "protologtool"
-        const val VERSION = "1.0.0"
-        const val IS_ENABLED_METHOD = "isEnabled"
-        const val IS_LOG_TO_LOGCAT_METHOD = "isLogToLogcat"
-        const val IS_LOG_TO_ANY_METHOD = "isLogToAny"
-        const val GET_TAG_METHOD = "getTag"
-        const val ENUM_VALUES_METHOD = "values"
-}
diff --git a/tools/protologtool/src/com/android/protologtool/LogGroup.kt b/tools/protologtool/src/com/android/protologtool/LogGroup.kt
deleted file mode 100644
index 42a37a2..0000000
--- a/tools/protologtool/src/com/android/protologtool/LogGroup.kt
+++ /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 com.android.protologtool
-
-data class LogGroup(
-    val name: String,
-    val enabled: Boolean,
-    val textEnabled: Boolean,
-    val tag: String
-)
diff --git a/tools/protologtool/src/com/android/protologtool/LogLevel.kt b/tools/protologtool/src/com/android/protologtool/LogLevel.kt
deleted file mode 100644
index dc29557..0000000
--- a/tools/protologtool/src/com/android/protologtool/LogLevel.kt
+++ /dev/null
@@ -1,37 +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.protologtool
-
-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/protologtool/LogParser.kt b/tools/protologtool/src/com/android/protologtool/LogParser.kt
deleted file mode 100644
index 4d0eb0e..0000000
--- a/tools/protologtool/src/com/android/protologtool/LogParser.kt
+++ /dev/null
@@ -1,112 +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.protologtool
-
-import com.android.json.stream.JsonReader
-import com.android.server.wm.ProtoLogMessage
-import com.android.server.wm.WindowManagerLogFileProto
-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 =
-                WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
-                        WindowManagerLogFileProto.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 = CodeUtils.parseFormatString(format)
-        try {
-            argTypes.forEach {
-                when (it) {
-                    CodeUtils.LogDataTypes.BOOLEAN -> args.add(boolParamsIt.next())
-                    CodeUtils.LogDataTypes.LONG -> args.add(longParamsIt.next())
-                    CodeUtils.LogDataTypes.DOUBLE -> args.add(doubleParamsIt.next())
-                    CodeUtils.LogDataTypes.STRING -> args.add(strParmIt.next())
-                }
-            }
-        } 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 = WindowManagerLogFileProto.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/protologtool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt
deleted file mode 100644
index 29d8ae5..0000000
--- a/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt
+++ /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.protologtool
-
-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/protologtool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
deleted file mode 100644
index 42a75f8..0000000
--- a/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
+++ /dev/null
@@ -1,23 +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.protologtool
-
-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/protologtool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt
deleted file mode 100644
index 664c8a6..0000000
--- a/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.protologtool
-
-import com.android.protologtool.Constants.ENUM_VALUES_METHOD
-import com.android.protologtool.Constants.GET_TAG_METHOD
-import com.android.protologtool.Constants.IS_ENABLED_METHOD
-import com.android.protologtool.Constants.IS_LOG_TO_LOGCAT_METHOD
-import java.io.File
-import java.lang.RuntimeException
-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<Enum<*>> {
-        val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD)
-        @Suppress("UNCHECKED_CAST")
-        return (valuesMethod.invoke(null) as Array<Enum<*>>).toList()
-    }
-
-    private fun getLogGroupFromEnumValue(group: Any, clazz: Class<*>): LogGroup {
-        val enabled = clazz.getMethod(IS_ENABLED_METHOD).invoke(group) as Boolean
-        val textEnabled = clazz.getMethod(IS_LOG_TO_LOGCAT_METHOD).invoke(group) as Boolean
-        val tag = clazz.getMethod(GET_TAG_METHOD).invoke(group) as String
-        val name = (group as Enum<*>).name
-        return LogGroup(name, enabled, textEnabled, tag)
-    }
-
-    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 getLogGroupFromEnumValue(group, clazz)
-            }.toMap()
-        } catch (ex: ReflectiveOperationException) {
-            throw RuntimeException("Unable to load ProtoLogGroup enum class", ex)
-        }
-    }
-}
diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt
deleted file mode 100644
index 485a047..0000000
--- a/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt
+++ /dev/null
@@ -1,94 +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.protologtool
-
-import com.android.protologtool.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 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 code = StaticJavaParser.parse(file)
-            val outSrc = transformer.processClass(code)
-            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)
-            builder.processClass(StaticJavaParser.parse(file))
-        }
-        val out = FileOutputStream(command.viewerConfigJsonArg)
-        out.write(builder.build().toByteArray())
-        out.close()
-    }
-
-    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/protologtool/SourceTransformer.kt b/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt
deleted file mode 100644
index 319a8170..0000000
--- a/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt
+++ /dev/null
@@ -1,162 +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.protologtool
-
-import com.android.protologtool.Constants.IS_LOG_TO_ANY_METHOD
-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.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.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.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 = CodeUtils.parseFormatString(messageString)
-            val typeMask = CodeUtils.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(type.type, varName,
-                            type.toType(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)) + '}'
-        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.")
-        }
-    }
-
-    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)
-    }
-
-    private val protoLogImplClassNode =
-            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
-
-    fun processClass(compilationUnit: CompilationUnit): String {
-        LexicalPreservingPrinter.setup(compilationUnit)
-        protoLogCallProcessor.process(compilationUnit, this)
-        return LexicalPreservingPrinter.print(compilationUnit)
-    }
-}
diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
deleted file mode 100644
index 8ce9a49..0000000
--- a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
+++ /dev/null
@@ -1,91 +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.protologtool
-
-import com.android.json.stream.JsonWriter
-import com.github.javaparser.ast.CompilationUnit
-import com.android.protologtool.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/protologtool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt
deleted file mode 100644
index 69cf92d..0000000
--- a/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt
+++ /dev/null
@@ -1,125 +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.protologtool
-
-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()) {
-            val key = jsonReader.nextName()
-            when (key) {
-                "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()) {
-            val key = jsonReader.nextName()
-            when (key) {
-                "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()) {
-            val key = jsonReader.nextName()
-            when (key) {
-                "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/protologtool/exceptions.kt b/tools/protologtool/src/com/android/protologtool/exceptions.kt
deleted file mode 100644
index 2199785..0000000
--- a/tools/protologtool/src/com/android/protologtool/exceptions.kt
+++ /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 com.android.protologtool
-
-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 : Exception {
-    constructor(message: String) : super(message)
-
-    constructor(message: String, ex: Exception) : super(message, ex)
-}
-
-class InvalidFormatStringException : Exception {
-    constructor(message: String) : super(message)
-
-    constructor(message: String, ex: Exception) : super(message, ex)
-}
-
-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..0acbc90
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
@@ -0,0 +1,182 @@
+/*
+ * 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(-1259556708, CodeUtils.hash("Test.java:50", "test",
+                LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+    }
+
+    @Test
+    fun hash_changeLocation() {
+        assertEquals(15793504, CodeUtils.hash("Test.java:10", "test2",
+                LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+    }
+
+    @Test
+    fun hash_changeLevel() {
+        assertEquals(-731772463, CodeUtils.hash("Test.java:50", "test",
+                LogLevel.ERROR, LogGroup("test", true, true, "TAG")))
+    }
+
+    @Test
+    fun hash_changeMessage() {
+        assertEquals(-2026343204, CodeUtils.hash("Test.java:50", "test2",
+                LogLevel.DEBUG, LogGroup("test", true, true, "TAG")))
+    }
+
+    @Test
+    fun hash_changeGroup() {
+        assertEquals(1607870166, CodeUtils.hash("Test.java:50", "test2",
+                LogLevel.DEBUG, LogGroup("test2", true, true, "TAG")))
+    }
+
+    @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..f221fbd
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -0,0 +1,448 @@
+/*
+ * 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 (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 805272208, 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 (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -154595499, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_NO_PARAMS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { org.example.ProtoLogImpl.w(TEST_GROUP, 1913810354, 0, "test", (Object[]) null); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 1922613844, 9, null, protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (org.example.ProtoLogImpl.isEnabled(TEST_GROUP)) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, 805272208, 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 const val PATH = "com.example.Test.java"
+    }
+
+    private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+    private val implPath = "org.example.ProtoLogImpl"
+    private val sourceJarWriter = SourceTransformer(implPath, 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("1922613844", 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(3, ifStmts.size)
+        val ifStmt = ifStmts[1]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("1922613844", 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("805272208", 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("1913810354", 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("1922613844", 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, PATH, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("$implPath.${Constants.IS_ENABLED_METHOD}(TEST_GROUP)",
+                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("805272208", 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, PATH, 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, PATH, 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..d3f8c76
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
@@ -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.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 GROUP1 = LogGroup("TEST_GROUP", true, true, TAG1)
+        private val GROUP2 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+        private val GROUP3 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+        private const val PATH = "/tmp/test.java"
+    }
+
+    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,
+                    GROUP1)
+            visitor.processCall(MethodCallExpr(), TEST2.messageString, LogLevel.DEBUG,
+                    GROUP2)
+            visitor.processCall(MethodCallExpr(), TEST3.messageString, LogLevel.ERROR,
+                    GROUP3)
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        configBuilder.processClass(dummyCompilationUnit, PATH)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(3, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
+	           TEST1.messageString, LogLevel.INFO, GROUP1)])
+        assertEquals(TEST2, parsedConfig[CodeUtils.hash(PATH, TEST2.messageString,
+	           LogLevel.DEBUG, GROUP2)])
+        assertEquals(TEST3, parsedConfig[CodeUtils.hash(PATH, TEST3.messageString,
+	           LogLevel.ERROR, GROUP3)])
+    }
+
+    @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,
+                    GROUP1)
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    GROUP1)
+            visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
+                    GROUP1)
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        configBuilder.processClass(dummyCompilationUnit, PATH)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(1, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
+            	   LogLevel.INFO, GROUP1)])
+    }
+
+    @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,
+                    GROUP1)
+            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, PATH)
+
+        val parsedConfig = parseConfig(configBuilder.build())
+        assertEquals(2, parsedConfig.size)
+        assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
+	           LogLevel.INFO, GROUP1)])
+        assertEquals(TEST3, parsedConfig[CodeUtils.hash(PATH, TEST3.messageString,
+	           LogLevel.ERROR, LogGroup("DEBUG_GROUP", true, false, TAG2))])
+    }
+}
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/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
deleted file mode 100644
index 82daa73..0000000
--- a/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
+++ /dev/null
@@ -1,206 +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.protologtool
-
-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)
-    }
-
-    @Test
-    fun parseFormatString() {
-        val str = "%b %d %o %x %f %e %g %s %%"
-        val out = CodeUtils.parseFormatString(str)
-        assertEquals(listOf(
-                CodeUtils.LogDataTypes.BOOLEAN,
-                CodeUtils.LogDataTypes.LONG,
-                CodeUtils.LogDataTypes.LONG,
-                CodeUtils.LogDataTypes.LONG,
-                CodeUtils.LogDataTypes.DOUBLE,
-                CodeUtils.LogDataTypes.DOUBLE,
-                CodeUtils.LogDataTypes.DOUBLE,
-                CodeUtils.LogDataTypes.STRING
-        ), out)
-    }
-
-    @Test(expected = InvalidFormatStringException::class)
-    fun parseFormatString_invalid() {
-        val str = "%q"
-        CodeUtils.parseFormatString(str)
-    }
-
-    @Test
-    fun logDataTypesToBitMask() {
-        val types = listOf(CodeUtils.LogDataTypes.STRING, CodeUtils.LogDataTypes.DOUBLE,
-                CodeUtils.LogDataTypes.LONG, CodeUtils.LogDataTypes.BOOLEAN)
-        val mask = CodeUtils.logDataTypesToBitMask(types)
-        assertEquals(0b11011000, mask)
-    }
-
-    @Test(expected = InvalidFormatStringException::class)
-    fun logDataTypesToBitMask_toManyParams() {
-        val types = mutableListOf<CodeUtils.LogDataTypes>()
-        for (i in 0..16) {
-            types.add(CodeUtils.LogDataTypes.STRING)
-        }
-        CodeUtils.logDataTypesToBitMask(types)
-    }
-}
diff --git a/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt
deleted file mode 100644
index c1cd473..0000000
--- a/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt
+++ /dev/null
@@ -1,250 +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.protologtool
-
-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/protologtool/LogParserTest.kt b/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt
deleted file mode 100644
index 7106ea6..0000000
--- a/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt
+++ /dev/null
@@ -1,187 +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.protologtool
-
-import com.android.json.stream.JsonReader
-import com.android.server.wm.ProtoLogMessage
-import com.android.server.wm.WindowManagerLogFileProto
-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: WindowManagerLogFileProto.Builder): InputStream {
-        logBuilder.setVersion(Constants.VERSION)
-        logBuilder.magicNumber =
-                WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
-                        WindowManagerLogFileProto.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 = WindowManagerLogFileProto.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 = WindowManagerLogFileProto.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 = WindowManagerLogFileProto.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 = WindowManagerLogFileProto.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 = WindowManagerLogFileProto.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 = WindowManagerLogFileProto.newBuilder()
-        logBuilder.setVersion("invalid")
-        logBuilder.magicNumber =
-                WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or
-                        WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong()
-        val stream = logBuilder.build().toByteArray().inputStream()
-
-        parser.parse(stream, getConfigDummyStream(), printStream)
-    }
-
-    @Test
-    fun parse_noConfig() {
-        val logBuilder = WindowManagerLogFileProto.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/protologtool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt
deleted file mode 100644
index dcb1f7f..0000000
--- a/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt
+++ /dev/null
@@ -1,226 +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.protologtool
-
-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/protologtool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt
deleted file mode 100644
index 7b8dd9a..0000000
--- a/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt
+++ /dev/null
@@ -1,373 +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.protologtool
-
-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"
-        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_NO_PARAMS = """
-            package org.example;
-
-            class Test {
-                void test() {
-                    ProtoLog.w(TEST_GROUP, "test");
-                }
-            }
-            """.trimIndent()
-
-        /* ktlint-disable max-line-length */
-        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_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() {
-        val 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(code)
-
-        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_textEnabledMultiline() {
-        val 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(code)
-
-        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() {
-        val 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(code)
-
-        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() {
-        val 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(code)
-
-        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() {
-        val 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(code)
-
-        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() {
-        val 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(code)
-
-        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() {
-        val 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(code)
-
-        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/protologtool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
deleted file mode 100644
index 53d2e8b..0000000
--- a/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
+++ /dev/null
@@ -1,120 +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.protologtool
-
-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/protologtool/ViewerConfigParserTest.kt b/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt
deleted file mode 100644
index c0cea73..0000000
--- a/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt
+++ /dev/null
@@ -1,327 +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.protologtool
-
-import com.android.json.stream.JsonReader
-import org.junit.Test
-import java.io.StringReader
-import org.junit.Assert.assertEquals
-
-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"
-            }
-          }
-        }
-        """
-        val config = parser.parseConfig(getJSONReader(json))
-    }
-}
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 56a242f..5ac9dfd 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -137,7 +137,6 @@
     }
     }
 
-    log("No errors.\n\n");
     return true;
 }
 
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/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index a37de00..00895e8 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -55,13 +55,10 @@
 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.ArrayUtils;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.Protocol;
 import com.android.server.net.NetworkPinner;
 
 import dalvik.system.CloseGuard;
@@ -75,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;
 
 /**
@@ -1128,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;
 
@@ -1732,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)
@@ -2502,25 +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) {
-                if (TextUtils.isEmpty(country)) return;
-                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.
     *
@@ -2645,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);
+        }
     }
 
     /**
@@ -3081,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.
@@ -3173,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
@@ -3217,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
@@ -3275,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)}.
      *
@@ -3708,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;
     }
 
     /**
@@ -3852,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);
     }
 
     /**
@@ -3878,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);
     }
 
     /**
@@ -3909,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);
+        }
     }
 
     /**
@@ -3933,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);
+        }
     }
 
     /**
@@ -3943,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
@@ -3951,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);
+            }
+        }
     }
 
     /**
@@ -4009,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
@@ -4486,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
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/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/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);
+    }
 }