Merge "Security Fix: Race Condition + NPE"
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 0000000..b061ccf
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
+Ember Rose <emberr@google.com> <ashleyrose@google.com>
diff --git a/Android.bp b/Android.bp
index 121decb..31dbaad 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: {
@@ -234,6 +242,11 @@
             "wifi/java",
         ],
     },
+
+    required: [
+        // TODO: remove gps_debug when the build system propagates "required" properly.
+        "gps_debug.conf",
+    ],
 }
 
 // Collection of classes that are generated from non-Java files that are not listed in
@@ -283,15 +296,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",
@@ -307,11 +317,6 @@
 
     static_libs: ["framework-internal-utils"],
 
-    required: [
-        // TODO: remove gps_debug when the build system propagates "required" properly.
-        "gps_debug.conf",
-    ],
-
     dxflags: [
         "--core-library",
         "--multi-dex",
@@ -359,6 +364,7 @@
 java_library {
     name: "framework-minus-apex",
     defaults: ["framework-defaults"],
+    srcs: [":framework-non-updatable-sources"],
     javac_shard_size: 150,
 }
 
@@ -379,8 +385,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 +516,6 @@
     sdk_version: "core_platform",
     static_libs: [
         "libphonenumber-platform",
-        "nist-sip",
         "tagsoup",
         "rappor",
         "libtextclassifier-java",
@@ -884,10 +897,21 @@
     // replacement (with $1, $2 backreferences to the regex groups)
     "'$$1https://docs.oracle.com/javase/8/docs/$$2\">' "
 
+packages_to_document = [
+    "android",
+    "java",
+    "javax",
+    "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 +931,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 +976,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 +988,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 +999,7 @@
         "api-versions-jars-dir",
     ],
     sdk_version: "core_platform",
+    filter_packages: packages_to_document,
 }
 
 droidstubs {
@@ -1299,7 +1324,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 +1498,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 +1511,25 @@
         targets: ["droidcore"],
     },
 }
+
+// Avoid including Parcelable classes as we don't want to have two copies of
+// Parcelable cross the process.
+filegroup {
+    name: "framework-telephony-stack-shared-srcs",
+    srcs: [
+        "core/java/android/os/RegistrantList.java",
+        "core/java/android/os/Registrant.java",
+        "core/java/android/util/LocalLog.java",
+        "core/java/android/util/Slog.java",
+        "core/java/android/util/TimeUtils.java",
+        "core/java/com/android/internal/os/SomeArgs.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+        "core/java/com/android/internal/util/State.java",
+        "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/XmlUtils.java",
+        "core/java/com/android/internal/util/HexDump.java",
+        "core/java/com/android/internal/util/IndentingPrintWriter.java",
+        "core/java/com/android/internal/util/DumpUtils.java"
+    ],
+}
+
diff --git a/CleanSpec.mk b/CleanSpec.mk
index f311fc8..6a909c0 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -253,6 +253,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/DynamicAndroidInstallationService)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/DefaultContainerService)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/CaptivePortalLogin)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/ext.jar)
 # ******************************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
 # ******************************************************************
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 2b6f455..9cfc3d2 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -188,8 +188,8 @@
 
             @Override
             public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                    Rect minimizedHomeBounds) throws RemoteException {
+                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                    Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException {
                 final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2);
                 final boolean moveRecentsToTop = finishCase.second;
                 makeInterval();
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 4b7a7f6..32107b4 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -119,9 +119,20 @@
     }
 
     @Test
+    public void createUser() {
+        while (mRunner.keepRunning()) {
+            final int userId = createUserNoFlags();
+
+            mRunner.pauseTiming();
+            removeUser(userId);
+            mRunner.resumeTiming();
+        }
+    }
+
+    @Test
     public void createAndStartUser() throws Exception {
         while (mRunner.keepRunning()) {
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
 
             final CountDownLatch latch = new CountDownLatch(1);
             registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
@@ -140,7 +151,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             mRunner.resumeTiming();
 
             switchUser(userId);
@@ -197,7 +208,7 @@
     public void stopUser() throws Exception {
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             final CountDownLatch latch = new CountDownLatch(1);
             registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userId);
             mIam.startUserInBackground(userId);
@@ -217,7 +228,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser();
+            final int userId = createUserNoFlags();
             final CountDownLatch latch = new CountDownLatch(1);
             registerUserSwitchObserver(null, latch, userId);
             mRunner.resumeTiming();
@@ -237,7 +248,7 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
-            final int userId = createUser(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
+            final int userId = createUserWithFlags(UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO);
             switchUser(userId);
             final CountDownLatch latch = new CountDownLatch(1);
             InstrumentationRegistry.getContext().registerReceiver(new BroadcastReceiver() {
@@ -396,7 +407,6 @@
     public void managedProfileCreateUnlockInstallAndLaunchApp() throws Exception {
         assumeTrue(mHasManagedUserFeature);
 
-        final String packageName = "perftests.multiuser.apps.dummyapp";
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             WindowManagerGlobal.getWindowManagerService().dismissKeyguard(null, null);
@@ -433,12 +443,12 @@
     }
 
     /** Creates a new user, returning its userId. */
-    private int createUser() {
-        return createUser(0);
+    private int createUserNoFlags() {
+        return createUserWithFlags(/* flags= */ 0);
     }
 
     /** Creates a new user with the given flags, returning its userId. */
-    private int createUser(int flags) {
+    private int createUserWithFlags(int flags) {
         int userId = mUm.createUser("TestUser", flags).id;
         mUsersToRemove.add(userId);
         return userId;
@@ -501,7 +511,7 @@
     private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception {
         final int origUser = mAm.getCurrentUser();
         // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
-        final int testUser = createUser();
+        final int testUser = createUserNoFlags();
         final CountDownLatch latch1 = new CountDownLatch(1);
         registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
         mAm.switchUser(testUser);
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index d0e38b4..65aaf20 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -67,6 +67,7 @@
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.KeyValueListParser;
 import android.util.MutableLong;
 import android.util.Pair;
@@ -79,7 +80,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
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/api/current.txt b/api/current.txt
index 99ae1c8..d688220 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2812,12 +2812,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 +2836,7 @@
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     method public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated protected boolean onGesture(int);
-    method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureInfo);
+    method public boolean onGesture(@NonNull android.accessibilityservice.AccessibilityGestureEvent);
     method public abstract void onInterrupt();
     method protected boolean onKeyEvent(android.view.KeyEvent);
     method protected void onServiceConnected();
@@ -4279,14 +4279,21 @@
     method public void checkPackage(int, @NonNull String);
     method public void finishOp(@NonNull String, int, @NonNull String);
     method public boolean isOpActive(@NonNull String, int, @NonNull String);
-    method public int noteOp(@NonNull String, int, @NonNull String);
-    method public int noteOpNoThrow(@NonNull String, int, @NonNull String);
-    method public int noteProxyOp(@NonNull String, @NonNull String);
-    method public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
-    method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+    method @Deprecated public int noteOp(@NonNull String, int, @NonNull String);
+    method public int noteOp(@NonNull String, int, @Nullable String, @Nullable String);
+    method @Deprecated public int noteOpNoThrow(@NonNull String, int, @NonNull String);
+    method public int noteOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
+    method @Deprecated public int noteProxyOp(@NonNull String, @NonNull String);
+    method public int noteProxyOp(@NonNull String, @Nullable String, int, @Nullable String);
+    method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
+    method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
+    method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String);
     method public static String permissionToOp(String);
-    method public int startOp(@NonNull String, int, @NonNull String);
-    method public int startOpNoThrow(@NonNull String, int, @NonNull String);
+    method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
+    method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
+    method public int startOp(@NonNull String, int, @Nullable String, @Nullable String);
+    method @Deprecated public int startOpNoThrow(@NonNull String, int, @NonNull String);
+    method public int startOpNoThrow(@NonNull String, int, @NonNull String, @Nullable String);
     method public void startWatchingActive(@NonNull String[], @NonNull java.util.concurrent.Executor, @NonNull android.app.AppOpsManager.OnOpActiveChangedListener);
     method public void startWatchingMode(@NonNull String, @Nullable String, @NonNull android.app.AppOpsManager.OnOpChangedListener);
     method public void startWatchingMode(@NonNull String, @Nullable String, int, @NonNull android.app.AppOpsManager.OnOpChangedListener);
@@ -4338,6 +4345,14 @@
     field public static final int WATCH_FOREGROUND_CHANGES = 1; // 0x1
   }
 
+  public abstract static class AppOpsManager.AppOpsCollector {
+    ctor public AppOpsManager.AppOpsCollector();
+    method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor();
+    method public abstract void onAsyncNoted(@NonNull android.app.AsyncNotedAppOp);
+    method public abstract void onNoted(@NonNull android.app.SyncNotedAppOp);
+    method public abstract void onSelfNoted(@NonNull android.app.SyncNotedAppOp);
+  }
+
   public static interface AppOpsManager.OnOpActiveChangedListener {
     method public void onOpActiveChanged(@NonNull String, int, @NonNull String, boolean);
   }
@@ -4458,6 +4473,17 @@
     field public String serviceDetails;
   }
 
+  public final class AsyncNotedAppOp implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getMessage();
+    method @Nullable public String getNotingPackageName();
+    method @IntRange(from=0) public int getNotingUid();
+    method @NonNull public String getOp();
+    method @IntRange(from=0) public long getTime();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
+  }
+
   public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable {
     ctor public AuthenticationRequiredException(Throwable, android.app.PendingIntent);
     method public int describeContents();
@@ -5294,10 +5320,12 @@
     ctor public Notification(android.os.Parcel);
     method public android.app.Notification clone();
     method public int describeContents();
+    method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean);
     method public boolean getAllowSystemGeneratedContextualActions();
     method public int getBadgeIconType();
     method @Nullable public android.app.Notification.BubbleMetadata getBubbleMetadata();
     method public String getChannelId();
+    method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions();
     method public String getGroup();
     method public int getGroupAlertBehavior();
     method public android.graphics.drawable.Icon getLargeIcon();
@@ -5693,6 +5721,7 @@
     method public String getDataMimeType();
     method public android.net.Uri getDataUri();
     method public android.os.Bundle getExtras();
+    method @NonNull public static java.util.List<android.app.Notification.MessagingStyle.Message> getMessagesFromBundleArray(@Nullable android.os.Parcelable[]);
     method @Deprecated public CharSequence getSender();
     method @Nullable public android.app.Person getSenderPerson();
     method public CharSequence getText();
@@ -5790,6 +5819,7 @@
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
+    method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
     method public void setDescription(String);
@@ -6267,6 +6297,10 @@
   public class StatusBarManager {
   }
 
+  public final class SyncNotedAppOp {
+    method @NonNull public String getOp();
+  }
+
   @Deprecated public class TabActivity extends android.app.ActivityGroup {
     ctor @Deprecated public TabActivity();
     method @Deprecated public android.widget.TabHost getTabHost();
@@ -7182,18 +7216,18 @@
     method @Nullable public android.view.autofill.AutofillValue getAutofillValue();
     method public android.app.assist.AssistStructure.ViewNode getChildAt(int);
     method public int getChildCount();
-    method public String getClassName();
-    method public CharSequence getContentDescription();
+    method @Nullable public String getClassName();
+    method @Nullable public CharSequence getContentDescription();
     method public float getElevation();
-    method public android.os.Bundle getExtras();
+    method @Nullable public android.os.Bundle getExtras();
     method public int getHeight();
-    method public String getHint();
+    method @Nullable public String getHint();
     method @Nullable public String getHintIdEntry();
     method @Nullable public android.view.ViewStructure.HtmlInfo getHtmlInfo();
     method public int getId();
-    method public String getIdEntry();
-    method public String getIdPackage();
-    method public String getIdType();
+    method @Nullable public String getIdEntry();
+    method @Nullable public String getIdPackage();
+    method @Nullable public String getIdType();
     method public int getImportantForAutofill();
     method public int getInputType();
     method public int getLeft();
@@ -7203,12 +7237,12 @@
     method public int getMinTextEms();
     method public int getScrollX();
     method public int getScrollY();
-    method public CharSequence getText();
+    method @Nullable public CharSequence getText();
     method public int getTextBackgroundColor();
     method public int getTextColor();
     method @Nullable public String getTextIdEntry();
-    method public int[] getTextLineBaselines();
-    method public int[] getTextLineCharOffsets();
+    method @Nullable public int[] getTextLineBaselines();
+    method @Nullable public int[] getTextLineCharOffsets();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
     method public float getTextSize();
@@ -7844,6 +7878,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();
@@ -16872,8 +16907,13 @@
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract String getId();
     method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
+    method public void setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException;
+    field public static final int AUDIO_RESTRICTION_NONE = 0; // 0x0
+    field public static final int AUDIO_RESTRICTION_VIBRATION = 1; // 0x1
+    field public static final int AUDIO_RESTRICTION_VIBRATION_SOUND = 3; // 0x3
     field public static final int TEMPLATE_MANUAL = 6; // 0x6
     field public static final int TEMPLATE_PREVIEW = 1; // 0x1
     field public static final int TEMPLATE_RECORD = 3; // 0x3
@@ -23928,6 +23968,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);
@@ -29422,7 +29463,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();
@@ -41702,6 +41744,7 @@
     method public int getUid();
     method public android.os.UserHandle getUser();
     method @Deprecated public int getUserId();
+    method public boolean isAppGroup();
     method public boolean isClearable();
     method public boolean isGroup();
     method public boolean isOngoing();
@@ -50408,6 +50451,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();
@@ -50752,6 +50796,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);
@@ -52106,6 +52151,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
@@ -52223,6 +52269,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();
@@ -52314,6 +52361,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);
@@ -52626,7 +52674,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();
@@ -52650,7 +52698,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);
@@ -52994,6 +53042,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 e87007f..0d55b81 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -140,6 +140,7 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
@@ -521,8 +522,6 @@
   }
 
   public class Notification implements android.os.Parcelable {
-    method @Nullable public android.util.Pair<android.app.RemoteInput,android.app.Notification.Action> findRemoteInputActionPair(boolean);
-    method @NonNull public java.util.List<android.app.Notification.Action> getContextualActions();
     field public static final String CATEGORY_CAR_EMERGENCY = "car_emergency";
     field public static final String CATEGORY_CAR_INFORMATION = "car_information";
     field public static final String CATEGORY_CAR_WARNING = "car_warning";
@@ -531,10 +530,6 @@
     field public static final int FLAG_AUTOGROUP_SUMMARY = 1024; // 0x400
   }
 
-  public static final class Notification.MessagingStyle.Message {
-    method @Nullable public static android.app.Notification.MessagingStyle.Message getMessageFromBundle(@NonNull android.os.Bundle);
-  }
-
   public static final class Notification.TvExtender implements android.app.Notification.Extender {
     ctor public Notification.TvExtender();
     ctor public Notification.TvExtender(android.app.Notification);
@@ -3029,6 +3024,26 @@
     field public static final int STATUS_OK = 0; // 0x0
   }
 
+  public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+    field @NonNull public final String description;
+    field public final int id;
+    field @NonNull public final String implementor;
+    field public final int maxBufferMs;
+    field public final int maxKeyphrases;
+    field public final int maxSoundModels;
+    field public final int maxUsers;
+    field public final int powerConsumptionMw;
+    field public final int recognitionModes;
+    field public final boolean returnsTriggerInEvent;
+    field public final boolean supportsCaptureTransition;
+    field public final boolean supportsConcurrentCapture;
+    field @NonNull public final java.util.UUID uuid;
+    field public final int version;
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     method @Nullable public android.media.AudioFormat getCaptureFormat();
     method public int getCaptureSession();
@@ -3771,6 +3786,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
     method public int getDetectionServiceOperationsTimeout();
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @Nullable public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -5500,7 +5516,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();
@@ -5692,6 +5708,7 @@
     method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
     method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
     method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method @BinderThread public void onUpdateUserSensitive();
     field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
   }
 
@@ -6104,6 +6121,30 @@
     field public static final String WAIT_TIME_RETRY = "wait_time";
   }
 
+  public static final class Telephony.CellBroadcasts implements android.provider.BaseColumns {
+    field public static final String CID = "cid";
+    field public static final String CMAS_CATEGORY = "cmas_category";
+    field public static final String CMAS_CERTAINTY = "cmas_certainty";
+    field public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+    field public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+    field public static final String CMAS_SEVERITY = "cmas_severity";
+    field public static final String CMAS_URGENCY = "cmas_urgency";
+    field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DEFAULT_SORT_ORDER = "date DESC";
+    field public static final String DELIVERY_TIME = "date";
+    field public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+    field public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+    field public static final String LAC = "lac";
+    field public static final String LANGUAGE_CODE = "language";
+    field public static final String MESSAGE_BODY = "body";
+    field public static final String MESSAGE_FORMAT = "format";
+    field public static final String MESSAGE_PRIORITY = "priority";
+    field public static final String MESSAGE_READ = "read";
+    field public static final String PLMN = "plmn";
+    field public static final String SERIAL_NUMBER = "serial_number";
+    field public static final String SERVICE_CATEGORY = "service_category";
+  }
+
   public final class TimeZoneRulesDataContract {
     field public static final String AUTHORITY = "com.android.timezone";
   }
@@ -6665,10 +6706,6 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.SnoozeCriterion> CREATOR;
   }
 
-  public class StatusBarNotification implements android.os.Parcelable {
-    method public boolean isAppGroup();
-  }
-
 }
 
 package android.service.oemlock {
@@ -7199,6 +7236,13 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR;
   }
 
+  public class CallerInfo {
+    method @Nullable public android.net.Uri getContactDisplayPhotoUri();
+    method public long getContactId();
+    method @Nullable public String getName();
+    method @Nullable public String getPhoneNumber();
+  }
+
   public class CarrierConfigManager {
     method @NonNull public static android.os.PersistableBundle getDefaultConfig();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
@@ -7691,6 +7735,24 @@
     field public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
   }
 
+  public final class ModemActivityInfo implements android.os.Parcelable {
+    ctor public ModemActivityInfo(long, int, int, @NonNull int[], int);
+    method public int describeContents();
+    method public int getIdleTimeMillis();
+    method public int getReceiveTimeMillis();
+    method public int getSleepTimeMillis();
+    method public long getTimestamp();
+    method @NonNull public java.util.List<android.telephony.ModemActivityInfo.TransmitPower> getTransmitPowerInfo();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ModemActivityInfo> CREATOR;
+    field public static final int TX_POWER_LEVELS = 5; // 0x5
+  }
+
+  public class ModemActivityInfo.TransmitPower {
+    method @NonNull public android.util.Range<java.lang.Integer> getPowerRangeInDbm();
+    method public int getTimeInMillis();
+  }
+
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method public int describeContents();
     method public int getAccessNetworkTechnology();
@@ -7792,6 +7854,8 @@
     field public static final int LISTEN_CALL_ATTRIBUTES_CHANGED = 67108864; // 0x4000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES = 134217728; // 0x8000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000
+    field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
     field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
@@ -7936,7 +8000,125 @@
     field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
   }
 
+  public final class SmsCbCmasInfo implements android.os.Parcelable {
+    ctor public SmsCbCmasInfo(int, int, int, int, int, int);
+    method public int describeContents();
+    method public int getCategory();
+    method public int getCertainty();
+    method public int getMessageClass();
+    method public int getResponseType();
+    method public int getSeverity();
+    method public int getUrgency();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CMAS_CATEGORY_CBRNE = 10; // 0xa
+    field public static final int CMAS_CATEGORY_ENV = 7; // 0x7
+    field public static final int CMAS_CATEGORY_FIRE = 5; // 0x5
+    field public static final int CMAS_CATEGORY_GEO = 0; // 0x0
+    field public static final int CMAS_CATEGORY_HEALTH = 6; // 0x6
+    field public static final int CMAS_CATEGORY_INFRA = 9; // 0x9
+    field public static final int CMAS_CATEGORY_MET = 1; // 0x1
+    field public static final int CMAS_CATEGORY_OTHER = 11; // 0xb
+    field public static final int CMAS_CATEGORY_RESCUE = 4; // 0x4
+    field public static final int CMAS_CATEGORY_SAFETY = 2; // 0x2
+    field public static final int CMAS_CATEGORY_SECURITY = 3; // 0x3
+    field public static final int CMAS_CATEGORY_TRANSPORT = 8; // 0x8
+    field public static final int CMAS_CATEGORY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CERTAINTY_LIKELY = 1; // 0x1
+    field public static final int CMAS_CERTAINTY_OBSERVED = 0; // 0x0
+    field public static final int CMAS_CERTAINTY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 3; // 0x3
+    field public static final int CMAS_CLASS_CMAS_EXERCISE = 5; // 0x5
+    field public static final int CMAS_CLASS_EXTREME_THREAT = 1; // 0x1
+    field public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 6; // 0x6
+    field public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0; // 0x0
+    field public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 4; // 0x4
+    field public static final int CMAS_CLASS_SEVERE_THREAT = 2; // 0x2
+    field public static final int CMAS_CLASS_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_RESPONSE_TYPE_ASSESS = 6; // 0x6
+    field public static final int CMAS_RESPONSE_TYPE_AVOID = 5; // 0x5
+    field public static final int CMAS_RESPONSE_TYPE_EVACUATE = 1; // 0x1
+    field public static final int CMAS_RESPONSE_TYPE_EXECUTE = 3; // 0x3
+    field public static final int CMAS_RESPONSE_TYPE_MONITOR = 4; // 0x4
+    field public static final int CMAS_RESPONSE_TYPE_NONE = 7; // 0x7
+    field public static final int CMAS_RESPONSE_TYPE_PREPARE = 2; // 0x2
+    field public static final int CMAS_RESPONSE_TYPE_SHELTER = 0; // 0x0
+    field public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_SEVERITY_EXTREME = 0; // 0x0
+    field public static final int CMAS_SEVERITY_SEVERE = 1; // 0x1
+    field public static final int CMAS_SEVERITY_UNKNOWN = -1; // 0xffffffff
+    field public static final int CMAS_URGENCY_EXPECTED = 1; // 0x1
+    field public static final int CMAS_URGENCY_IMMEDIATE = 0; // 0x0
+    field public static final int CMAS_URGENCY_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbCmasInfo> CREATOR;
+  }
+
+  public final class SmsCbEtwsInfo implements android.os.Parcelable {
+    ctor public SmsCbEtwsInfo(int, boolean, boolean, boolean, @Nullable byte[]);
+    method public int describeContents();
+    method @Nullable public byte[] getPrimaryNotificationSignature();
+    method public long getPrimaryNotificationTimestamp();
+    method public int getWarningType();
+    method public boolean isEmergencyUserAlert();
+    method public boolean isPopupAlert();
+    method public boolean isPrimary();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbEtwsInfo> CREATOR;
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0; // 0x0
+    field public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 2; // 0x2
+    field public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 4; // 0x4
+    field public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 3; // 0x3
+    field public static final int ETWS_WARNING_TYPE_TSUNAMI = 1; // 0x1
+    field public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; // 0xffffffff
+  }
+
+  public final class SmsCbLocation implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getCid();
+    method public int getLac();
+    method @NonNull public String getPlmn();
+    method public boolean isInLocationArea(@NonNull android.telephony.SmsCbLocation);
+    method public boolean isInLocationArea(@Nullable String, int, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbLocation> CREATOR;
+  }
+
+  public final class SmsCbMessage implements android.os.Parcelable {
+    ctor public SmsCbMessage(int, int, int, @NonNull android.telephony.SmsCbLocation, int, @Nullable String, @Nullable String, int, @Nullable android.telephony.SmsCbEtwsInfo, @Nullable android.telephony.SmsCbCmasInfo);
+    method @NonNull public static android.telephony.SmsCbMessage createFromCursor(@NonNull android.database.Cursor);
+    method public int describeContents();
+    method @Nullable public android.telephony.SmsCbCmasInfo getCmasWarningInfo();
+    method @NonNull public android.content.ContentValues getContentValues();
+    method @Nullable public android.telephony.SmsCbEtwsInfo getEtwsWarningInfo();
+    method public int getGeographicalScope();
+    method @Nullable public String getLanguageCode();
+    method @NonNull public android.telephony.SmsCbLocation getLocation();
+    method @Nullable public String getMessageBody();
+    method public int getMessageFormat();
+    method public int getMessagePriority();
+    method public long getReceivedTime();
+    method public int getSerialNumber();
+    method public int getServiceCategory();
+    method public boolean isCmasMessage();
+    method public boolean isEmergencyMessage();
+    method public boolean isEtwsMessage();
+    method public boolean needGeoFencingCheck();
+    method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SmsCbMessage> CREATOR;
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; // 0x3
+    field public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; // 0x0
+    field public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2; // 0x2
+    field public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP = 1; // 0x1
+    field public static final int MESSAGE_FORMAT_3GPP2 = 2; // 0x2
+    field public static final int MESSAGE_PRIORITY_EMERGENCY = 3; // 0x3
+    field public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; // 0x1
+    field public static final int MESSAGE_PRIORITY_NORMAL = 0; // 0x0
+    field public static final int MESSAGE_PRIORITY_URGENT = 2; // 0x2
+  }
+
   public final class SmsManager {
+    method public boolean disableCellBroadcastRange(int, int, int);
+    method public boolean enableCellBroadcastRange(int, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     field public static final int RESULT_CANCELLED = 23; // 0x17
     field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
@@ -7966,6 +8148,7 @@
   public class SubscriptionManager {
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
+    method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
     method public void requestEmbeddedSubscriptionInfoListRefresh();
     method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -8020,7 +8203,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);
@@ -8044,7 +8227,9 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
     method public int getSimApplicationState();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
     method public int getSimCardState();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimCardState(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Locale getSimLocale();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getSupportedRadioAccessFamily();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
@@ -8057,6 +8242,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
@@ -8064,7 +8250,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
     method public boolean needsOtaServiceProvisioning();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
@@ -8488,6 +8674,7 @@
     method public android.os.Bundle getCallExtras();
     method public int getCallType();
     method public static int getCallTypeFromVideoState(int);
+    method public int getCallerNumberVerificationStatus();
     method public int getEmergencyCallRouting();
     method public int getEmergencyServiceCategories();
     method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
@@ -8505,6 +8692,7 @@
     method public void setCallExtraBoolean(String, boolean);
     method public void setCallExtraInt(String, int);
     method public void setCallRestrictCause(int);
+    method public void setCallerNumberVerificationStatus(int);
     method public void setEmergencyCallRouting(int);
     method public void setEmergencyCallTesting(boolean);
     method public void setEmergencyServiceCategories(int);
@@ -8555,6 +8743,9 @@
     field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2
     field public static final int SERVICE_TYPE_NONE = 0; // 0x0
     field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1
+    field public static final int VERIFICATION_STATUS_FAILED = 2; // 0x2
+    field public static final int VERIFICATION_STATUS_NOT_VERIFIED = 0; // 0x0
+    field public static final int VERIFICATION_STATUS_PASSED = 1; // 0x1
   }
 
   public class ImsCallSessionListener {
@@ -9153,7 +9344,7 @@
     field public static final int STATE_UNAVAILABLE = 0; // 0x0
   }
 
-  public static class ImsFeature.Capabilities {
+  @Deprecated public static class ImsFeature.Capabilities {
     field @Deprecated protected int mCapabilities;
   }
 
@@ -9187,7 +9378,7 @@
   public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
     ctor public MmTelFeature.MmTelCapabilities();
     ctor @Deprecated public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities);
-    ctor public MmTelFeature.MmTelCapabilities(int);
+    ctor public MmTelFeature.MmTelCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void addCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final boolean isCapable(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
     method public final void removeCapabilities(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int);
@@ -9329,14 +9520,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 b2ed91b..34312f6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,8 +34,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);
   }
 
 }
@@ -97,8 +97,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeDockedStack(android.graphics.Rect, android.graphics.Rect);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeStack(int, android.graphics.Rect, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizePinnedStack(int, android.graphics.Rect, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeTask(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setDisplayToSingleTaskInstance(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
@@ -620,7 +619,6 @@
     method public int describeContents();
     method public static android.content.AutofillOptions forWhitelistingItself();
     method public boolean isAugmentedAutofillEnabled(@NonNull android.content.Context);
-    method public boolean isAutofillDisabledLocked(@NonNull android.content.ComponentName);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.AutofillOptions> CREATOR;
     field public long appDisabledExpiration;
@@ -2372,6 +2370,7 @@
     field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
     field public static final String LOW_POWER_MODE = "low_power";
     field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
+    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
     field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
@@ -2395,7 +2394,7 @@
     field public static final String LOCATION_ACCESS_CHECK_DELAY_MILLIS = "location_access_check_delay_millis";
     field public static final String LOCATION_ACCESS_CHECK_INTERVAL_MILLIS = "location_access_check_interval_millis";
     field public static final String NOTIFICATION_BADGING = "notification_badging";
-    field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+    field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
     field public static final String USER_SETUP_COMPLETE = "user_setup_complete";
     field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
@@ -3336,6 +3335,11 @@
     field @android.view.ViewDebug.ExportedProperty(flagMapping={@android.view.ViewDebug.FlagToString(mask=0x1, equals=0x1, name="FAKE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x2, equals=0x2, name="FORCE_HARDWARE_ACCELERATED"), @android.view.ViewDebug.FlagToString(mask=0x4, equals=0x4, name="WANTS_OFFSET_NOTIFICATIONS"), @android.view.ViewDebug.FlagToString(mask=0x10, equals=0x10, name="SHOW_FOR_ALL_USERS"), @android.view.ViewDebug.FlagToString(mask=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, equals=android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION, name="NO_MOVE_ANIMATION"), @android.view.ViewDebug.FlagToString(mask=0x80, equals=0x80, name="COMPATIBLE_WINDOW"), @android.view.ViewDebug.FlagToString(mask=0x100, equals=0x100, name="SYSTEM_ERROR"), @android.view.ViewDebug.FlagToString(mask=0x200, equals=0x200, name="INHERIT_TRANSLUCENT_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x400, equals=0x400, name="KEYGUARD"), @android.view.ViewDebug.FlagToString(mask=0x800, equals=0x800, name="DISABLE_WALLPAPER_TOUCH_EVENTS"), @android.view.ViewDebug.FlagToString(mask=0x1000, equals=0x1000, name="FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"), @android.view.ViewDebug.FlagToString(mask=0x2000, equals=0x2000, name="PRESERVE_GEOMETRY"), @android.view.ViewDebug.FlagToString(mask=0x4000, equals=0x4000, name="FORCE_DECOR_VIEW_VISIBILITY"), @android.view.ViewDebug.FlagToString(mask=0x8000, equals=0x8000, name="WILL_NOT_REPLACE_ON_RELAUNCH"), @android.view.ViewDebug.FlagToString(mask=0x10000, equals=0x10000, name="LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"), @android.view.ViewDebug.FlagToString(mask=0x20000, equals=0x20000, name="FORCE_DRAW_STATUS_BAR_BACKGROUND"), @android.view.ViewDebug.FlagToString(mask=0x40000, equals=0x40000, name="SUSTAINED_PERFORMANCE_MODE"), @android.view.ViewDebug.FlagToString(mask=0x80000, equals=0x80000, name="HIDE_NON_SYSTEM_OVERLAY_WINDOWS"), @android.view.ViewDebug.FlagToString(mask=0x100000, equals=0x100000, name="IS_ROUNDED_CORNERS_OVERLAY"), @android.view.ViewDebug.FlagToString(mask=0x400000, equals=0x400000, name="IS_SCREEN_DECOR"), @android.view.ViewDebug.FlagToString(mask=0x800000, equals=0x800000, name="STATUS_FORCE_SHOW_NAVIGATION"), @android.view.ViewDebug.FlagToString(mask=0x1000000, equals=0x1000000, name="COLOR_SPACE_AGNOSTIC")}) public int privateFlags;
   }
 
+  public class WindowlessViewRoot {
+    ctor public WindowlessViewRoot(android.content.Context, android.view.Display, android.view.SurfaceControl);
+    method public void addView(android.view.View, android.view.WindowManager.LayoutParams);
+  }
+
 }
 
 package android.view.accessibility {
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index f925023..8be95e4 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -22,7 +22,6 @@
         "libcutils",
         "libdl",
         "libhidlbase",
-        "libhwbinder",
         "liblog",
         "libnativeloader",
         "libutils",
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index db384ba..e1cb7ca 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -288,6 +288,48 @@
             dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
 
     SurfaceComposerClient::Transaction t;
+
+    // this guest property specifies multi-display IDs to show the boot animation
+    // multiple ids can be set with comma (,) as separator, for example:
+    // setprop boot.animation.displays 19260422155234049,19261083906282754
+    Vector<uint64_t> physicalDisplayIds;
+    char displayValue[PROPERTY_VALUE_MAX] = "";
+    property_get("boot.animation.displays", displayValue, "");
+    bool isValid = displayValue[0] != '\0';
+    if (isValid) {
+        char *p = displayValue;
+        while (*p) {
+            if (!isdigit(*p) && *p != ',') {
+                isValid = false;
+                break;
+            }
+            p ++;
+        }
+        if (!isValid)
+            SLOGE("Invalid syntax for the value of system prop: boot.animation.displays");
+    }
+    if (isValid) {
+        std::istringstream stream(displayValue);
+        for (PhysicalDisplayId id; stream >> id; ) {
+            physicalDisplayIds.add(id);
+            if (stream.peek() == ',')
+                stream.ignore();
+        }
+
+        // In the case of multi-display, boot animation shows on the specified displays
+        // in addition to the primary display
+        auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+        constexpr uint32_t LAYER_STACK = 0;
+        for (auto id : physicalDisplayIds) {
+            if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
+                sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(id);
+                if (token != nullptr)
+                    t.setDisplayLayerStack(token, LAYER_STACK);
+            }
+        }
+        t.setLayerStack(control, LAYER_STACK);
+    }
+
     t.setLayer(control, 0x40000000)
         .apply();
 
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index d4d5871..4c77ba4 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -69,6 +69,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libutils",
                 "libziparchive",
             ],
@@ -121,6 +122,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
@@ -163,6 +165,7 @@
             static_libs: [
                 "libandroidfw",
                 "libbase",
+                "libcutils",
                 "libidmap2",
                 "liblog",
                 "libutils",
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index 0b349e1..053b7bc 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -98,6 +98,7 @@
 }
 
 std::vector<std::string> PoliciesForPath(const std::string& apk_path) {
+  // clang-format off
   static const std::vector<std::pair<std::string, std::string>> values = {
       {"/odm/", kPolicyOdm},
       {"/oem/", kPolicyOem},
@@ -106,6 +107,7 @@
       {"/system_ext/", kPolicySystem},
       {"/vendor/", kPolicyVendor},
   };
+  // clang-format on
 
   std::vector<std::string> fulfilled_policies = {kPolicyPublic};
   for (auto const& pair : values) {
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h
index 1aab059..94d2af4 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.h
+++ b/cmds/idmap2/idmap2d/Idmap2Service.h
@@ -34,18 +34,19 @@
   }
 
   binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id,
-                              std::string* _aidl_return);
+                              std::string* _aidl_return) override;
 
   binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id,
-                             bool* _aidl_return);
+                             bool* _aidl_return) override;
 
   binder::Status verifyIdmap(const std::string& overlay_apk_path, int32_t fulfilled_policies,
-                             bool enforce_overlayable, int32_t user_id, bool* _aidl_return);
+                             bool enforce_overlayable, int32_t user_id,
+                             bool* _aidl_return) override;
 
   binder::Status createIdmap(const std::string& target_apk_path,
                              const std::string& overlay_apk_path, int32_t fulfilled_policies,
                              bool enforce_overlayable, int32_t user_id,
-                             std::unique_ptr<std::string>* _aidl_return);
+                             std::unique_ptr<std::string>* _aidl_return) override;
 };
 
 }  // namespace android::os
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 2c3e9d3..1a0d443 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -29,11 +29,12 @@
  public:
   explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~BinaryStreamVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   void Write16(uint16_t value);
diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
index 5111bb2..f0f141a 100644
--- a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h
@@ -33,11 +33,12 @@
  public:
   explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~PrettyPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   std::ostream& stream_;
diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
index 2e543d4..cd38971 100644
--- a/cmds/idmap2/include/idmap2/RawPrintVisitor.h
+++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h
@@ -34,11 +34,12 @@
  public:
   explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) {
   }
-  virtual void visit(const Idmap& idmap);
-  virtual void visit(const IdmapHeader& header);
-  virtual void visit(const IdmapData& data);
-  virtual void visit(const IdmapData::Header& header);
-  virtual void visit(const IdmapData::TypeEntry& type_entry);
+  ~RawPrintVisitor() override = default;
+  void visit(const Idmap& idmap) override;
+  void visit(const IdmapHeader& header) override;
+  void visit(const IdmapData& data) override;
+  void visit(const IdmapData::Header& header) override;
+  void visit(const IdmapData::TypeEntry& type_entry) override;
 
  private:
   void print(uint16_t value, const char* fmt, ...);
diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h
index adea329..a7c2f28 100644
--- a/cmds/idmap2/tests/TestHelpers.h
+++ b/cmds/idmap2/tests/TestHelpers.h
@@ -123,7 +123,7 @@
 
 class Idmap2Tests : public testing::Test {
  protected:
-  virtual void SetUp() {
+  void SetUp() override {
 #ifdef __ANDROID__
     tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX";
 #else
@@ -136,7 +136,7 @@
     idmap_path_ = tmp_dir_path_ + "/a.idmap";
   }
 
-  virtual void TearDown() {
+  void TearDown() override {
     EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0)
         << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno);
   }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 19fa640..05ff490 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -51,11 +51,6 @@
     srcs: [
         ":statsd_aidl",
         "src/active_config_list.proto",
-        "src/statsd_config.proto",
-        "src/uid_data.proto",
-        "src/FieldValue.cpp",
-        "src/hash.cpp",
-        "src/stats_log_util.cpp",
         "src/anomaly/AlarmMonitor.cpp",
         "src/anomaly/AlarmTracker.cpp",
         "src/anomaly/AnomalyTracker.cpp",
@@ -63,51 +58,58 @@
         "src/anomaly/subscriber_util.cpp",
         "src/condition/CombinationConditionTracker.cpp",
         "src/condition/condition_util.cpp",
-        "src/condition/SimpleConditionTracker.cpp",
         "src/condition/ConditionWizard.cpp",
-        "src/condition/StateTracker.cpp",
+        "src/condition/SimpleConditionTracker.cpp",
+        "src/condition/StateConditionTracker.cpp",
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
         "src/external/GpuStatsPuller.cpp",
         "src/external/Perfetto.cpp",
-        "src/external/StatsPuller.cpp",
+        "src/external/PowerStatsPuller.cpp",
+        "src/external/puller_util.cpp",
+        "src/external/ResourceHealthManagerPuller.cpp",
         "src/external/StatsCallbackPuller.cpp",
         "src/external/StatsCompanionServicePuller.cpp",
-        "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/PowerStatsPuller.cpp",
-        "src/external/ResourceHealthManagerPuller.cpp",
-        "src/external/TrainInfoPuller.cpp",
+        "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
-        "src/external/puller_util.cpp",
+        "src/external/SubsystemSleepStatePuller.cpp",
+        "src/external/TrainInfoPuller.cpp",
+        "src/FieldValue.cpp",
+        "src/guardrail/StatsdStats.cpp",
+        "src/hash.cpp",
+        "src/HashableDimensionKey.cpp",
         "src/logd/LogEvent.cpp",
         "src/logd/LogEventQueue.cpp",
         "src/matchers/CombinationLogMatchingTracker.cpp",
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
         "src/matchers/SimpleLogMatchingTracker.cpp",
-        "src/metrics/MetricProducer.cpp",
-        "src/metrics/EventMetricProducer.cpp",
         "src/metrics/CountMetricProducer.cpp",
-        "src/metrics/DurationMetricProducer.cpp",
-        "src/metrics/duration_helper/OringDurationTracker.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
-        "src/metrics/ValueMetricProducer.cpp",
+        "src/metrics/duration_helper/OringDurationTracker.cpp",
+        "src/metrics/DurationMetricProducer.cpp",
+        "src/metrics/EventMetricProducer.cpp",
         "src/metrics/GaugeMetricProducer.cpp",
-        "src/metrics/MetricsManager.cpp",
+        "src/metrics/MetricProducer.cpp",
         "src/metrics/metrics_manager_util.cpp",
+        "src/metrics/MetricsManager.cpp",
+        "src/metrics/ValueMetricProducer.cpp",
         "src/packages/UidMap.cpp",
-        "src/storage/StorageManager.cpp",
+        "src/shell/shell_config.proto",
+        "src/shell/ShellSubscriber.cpp",
+        "src/socket/StatsSocketListener.cpp",
+        "src/state/StateManager.cpp",
+        "src/state/StateTracker.cpp",
+        "src/stats_log_util.cpp",
+        "src/statscompanion_util.cpp",
+        "src/statsd_config.proto",
         "src/StatsLogProcessor.cpp",
         "src/StatsService.cpp",
-        "src/statscompanion_util.cpp",
+        "src/storage/StorageManager.cpp",
         "src/subscriber/IncidentdReporter.cpp",
         "src/subscriber/SubscriberReporter.cpp",
-        "src/HashableDimensionKey.cpp",
-        "src/guardrail/StatsdStats.cpp",
-        "src/socket/StatsSocketListener.cpp",
-        "src/shell/ShellSubscriber.cpp",
-        "src/shell/shell_config.proto",
+        "src/uid_data.proto",
     ],
 
     local_include_dirs: [
@@ -120,23 +122,23 @@
     ],
 
     shared_libs: [
-        "libbase",
-        "libbinder",
-        "libgraphicsenv",
-        "libincident",
-        "liblog",
-        "libutils",
-        "libservices",
-        "libprotoutil",
-        "libstatslog",
-        "libhidlbase",
         "android.frameworks.stats@1.0",
         "android.hardware.health@2.0",
+        "android.hardware.power.stats@1.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power.stats@1.0",
-        "libsysutils",
+        "libbase",
+        "libbinder",
         "libcutils",
+        "libgraphicsenv",
+        "libhidlbase",
+        "libincident",
+        "liblog",
+        "libprotoutil",
+        "libservices",
+        "libstatslog",
+        "libsysutils",
+        "libutils",
     ],
 }
 
@@ -204,56 +206,61 @@
     ],
 
     srcs: [
+        // atom_field_options.proto needs field_options.proto, but that is
+        // not included in libprotobuf-cpp-lite, so compile it here.
+        ":libprotobuf-internal-protos",
+
         "src/atom_field_options.proto",
         "src/atoms.proto",
-        "src/stats_log.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
         "tests/AlarmMonitor_test.cpp",
         "tests/anomaly/AlarmTracker_test.cpp",
         "tests/anomaly/AnomalyTracker_test.cpp",
+        "tests/condition/CombinationConditionTracker_test.cpp",
+        "tests/condition/ConditionTimer_test.cpp",
+        "tests/condition/SimpleConditionTracker_test.cpp",
+        "tests/condition/StateConditionTracker_test.cpp",
         "tests/ConfigManager_test.cpp",
-        "tests/external/puller_util_test.cpp",
+        "tests/e2e/Alarm_e2e_test.cpp",
+        "tests/e2e/Anomaly_count_e2e_test.cpp",
+        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
+        "tests/e2e/Attribution_e2e_test.cpp",
+        "tests/e2e/ConfigTtl_e2e_test.cpp",
+        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
+        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
+        "tests/e2e/MetricActivation_e2e_test.cpp",
+        "tests/e2e/MetricConditionLink_e2e_test.cpp",
+        "tests/e2e/PartialBucket_e2e_test.cpp",
+        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
+        "tests/e2e/WakelockDuration_e2e_test.cpp",
         "tests/external/GpuStatsPuller_test.cpp",
         "tests/external/IncidentReportArgs_test.cpp",
+        "tests/external/puller_util_test.cpp",
         "tests/external/StatsPuller_test.cpp",
+        "tests/FieldValue_test.cpp",
+        "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
+        "tests/log_event/LogEventQueue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
         "tests/LogEvent_test.cpp",
-        "tests/log_event/LogEventQueue_test.cpp",
-        "tests/MetricsManager_test.cpp",
-        "tests/StatsLogProcessor_test.cpp",
-        "tests/StatsService_test.cpp",
-        "tests/UidMap_test.cpp",
-        "tests/FieldValue_test.cpp",
-        "tests/condition/CombinationConditionTracker_test.cpp",
-        "tests/condition/SimpleConditionTracker_test.cpp",
-        "tests/condition/StateTracker_test.cpp",
-        "tests/condition/ConditionTimer_test.cpp",
-        "tests/metrics/OringDurationTracker_test.cpp",
-        "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/CountMetricProducer_test.cpp",
         "tests/metrics/DurationMetricProducer_test.cpp",
         "tests/metrics/EventMetricProducer_test.cpp",
-        "tests/metrics/ValueMetricProducer_test.cpp",
         "tests/metrics/GaugeMetricProducer_test.cpp",
-        "tests/guardrail/StatsdStats_test.cpp",
+        "tests/metrics/MaxDurationTracker_test.cpp",
         "tests/metrics/metrics_test_helper.cpp",
-        "tests/statsd_test_util.cpp",
-        "tests/storage/StorageManager_test.cpp",
-        "tests/e2e/WakelockDuration_e2e_test.cpp",
-        "tests/e2e/MetricActivation_e2e_test.cpp",
-        "tests/e2e/MetricConditionLink_e2e_test.cpp",
-        "tests/e2e/Alarm_e2e_test.cpp",
-        "tests/e2e/Attribution_e2e_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_push_test.cpp",
-        "tests/e2e/GaugeMetric_e2e_pull_test.cpp",
-        "tests/e2e/ValueMetric_pull_e2e_test.cpp",
-        "tests/e2e/Anomaly_count_e2e_test.cpp",
-        "tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
-        "tests/e2e/ConfigTtl_e2e_test.cpp",
-        "tests/e2e/PartialBucket_e2e_test.cpp",
-        "tests/e2e/DurationMetric_e2e_test.cpp",
+        "tests/metrics/OringDurationTracker_test.cpp",
+        "tests/metrics/ValueMetricProducer_test.cpp",
+        "tests/MetricsManager_test.cpp",
         "tests/shell/ShellSubscriber_test.cpp",
+        "tests/state/StateTracker_test.cpp",
+        "tests/statsd_test_util.cpp",
+        "tests/StatsLogProcessor_test.cpp",
+        "tests/StatsService_test.cpp",
+        "tests/storage/StorageManager_test.cpp",
+        "tests/UidMap_test.cpp",
     ],
 
     static_libs: [
@@ -262,11 +269,11 @@
     ],
 
     proto: {
-        type: "full",
+        type: "lite",
         include_dirs: ["external/protobuf/src"],
     },
 
-    shared_libs: ["libprotobuf-cpp-full"],
+    shared_libs: ["libprotobuf-cpp-lite"],
 
 }
 
@@ -279,21 +286,25 @@
     defaults: ["statsd_defaults"],
 
     srcs: [
+        // atom_field_options.proto needs field_options.proto, but that is
+        // not included in libprotobuf-cpp-lite, so compile it here.
+        ":libprotobuf-internal-protos",
+
+        "benchmark/duration_metric_benchmark.cpp",
+        "benchmark/filter_value_benchmark.cpp",
+        "benchmark/get_dimensions_for_condition_benchmark.cpp",
+        "benchmark/hello_world_benchmark.cpp",
+        "benchmark/log_event_benchmark.cpp",
+        "benchmark/main.cpp",
+        "benchmark/metric_util.cpp",
+        "benchmark/stats_write_benchmark.cpp",
         "src/atom_field_options.proto",
         "src/atoms.proto",
         "src/stats_log.proto",
-        "benchmark/main.cpp",
-        "benchmark/hello_world_benchmark.cpp",
-        "benchmark/log_event_benchmark.cpp",
-        "benchmark/stats_write_benchmark.cpp",
-        "benchmark/filter_value_benchmark.cpp",
-        "benchmark/get_dimensions_for_condition_benchmark.cpp",
-        "benchmark/metric_util.cpp",
-        "benchmark/duration_metric_benchmark.cpp",
     ],
 
     proto: {
-        type: "full",
+        type: "lite",
         include_dirs: ["external/protobuf/src"],
     },
 
@@ -315,7 +326,7 @@
     shared_libs: [
         "libgtest_prod",
         "libstatslog",
-        "libprotobuf-cpp-full",
+        "libprotobuf-cpp-lite",
     ],
 }
 
@@ -329,11 +340,11 @@
     },
 
     srcs: [
-        "src/stats_log.proto",
-        "src/statsd_config.proto",
         "src/atoms.proto",
         "src/shell/shell_config.proto",
         "src/shell/shell_data.proto",
+        "src/stats_log.proto",
+        "src/statsd_config.proto",
     ],
 
     static_libs: [
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 1185127..84a0607 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -116,28 +116,13 @@
 }
 
 bool isAttributionUidField(const FieldValue& value) {
-    int field = value.mField.getField() & 0xff007f;
-    if (field == 0x10001 && value.mValue.getType() == INT) {
-        return true;
-    }
-    return false;
+    return isAttributionUidField(value.mField, value.mValue);
 }
 
 int32_t getUidIfExists(const FieldValue& value) {
-    bool isUid = false;
     // the field is uid field if the field is the uid field in attribution node or marked as
     // is_uid in atoms.proto
-    if (isAttributionUidField(value)) {
-        isUid = true;
-    } else {
-        auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag());
-        if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-            int uidField = it->second;  // uidField is the field number in proto
-            isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField &&
-                    value.mValue.getType() == INT;
-        }
-    }
-
+    bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue);
     return isUid ? value.mValue.int_value : -1;
 }
 
@@ -153,7 +138,7 @@
     auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag());
 
     if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-        int uidField = it->second;
+        int uidField = it->second;  // uidField is the field number in proto
         return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField &&
                value.getType() == INT;
     }
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index af8b3af..5e156bb 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -59,6 +59,16 @@
     return JenkinsHashWhiten(hash);
 }
 
+bool filterValues(const Matcher& matcherField, const vector<FieldValue>& values, Value* output) {
+    for (const auto& value : values) {
+        if (value.mField.matches(matcherField)) {
+            (*output) = value.mValue;
+            return true;
+        }
+    }
+    return false;
+}
+
 bool filterValues(const vector<Matcher>& matcherFields, const vector<FieldValue>& values,
                   HashableDimensionKey* output) {
     size_t num_matches = 0;
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index 6f4941f..a123850 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -120,6 +120,13 @@
 android::hash_t hashDimension(const HashableDimensionKey& key);
 
 /**
+ * Returns true if a FieldValue field matches the matcher field.
+ * The value of the FieldValue is output.
+ */
+bool filterValues(const Matcher& matcherField, const std::vector<FieldValue>& values,
+                  Value* output);
+
+/**
  * Creating HashableDimensionKeys from FieldValues using matcher.
  *
  * This function may make modifications to the Field if the matcher has Position=FIRST,LAST or ALL
@@ -169,4 +176,4 @@
         return android::JenkinsHashWhiten(hash);
     }
 };
-}  // namespace std
\ No newline at end of file
+}  // namespace std
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index d1dcb5df..7ace44e 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -24,6 +24,7 @@
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
+#include <inttypes.h>
 #include <statslog.h>
 #include <time.h>
 
@@ -224,7 +225,7 @@
     }
 
     if (!mSubscriptions.empty()) {
-        ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.",
+        ALOGI("An anomaly (%" PRId64 ") %s has occurred! Informing subscribers.",
                 mAlert.id(), key.toString().c_str());
         informSubscribers(key, metricId, metricValue);
     } else {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 2732779..b71a86b 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -334,10 +334,13 @@
         BackGesture back_gesture_reported_reported = 224;
         UpdateEngineUpdateAttemptReported update_engine_update_attempt_reported = 225;
         UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226;
+        CameraActionEvent camera_action_event = 227;
+        AppCompatibilityChangeReported app_compatibility_change_reported =
+            228 [(allow_from_any_uid) = true];
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10064
+    // Next: 10065
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -403,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.
@@ -2617,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;
@@ -3064,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;
 
@@ -3074,7 +3080,7 @@
         ENTERED = 1;
         EXITED = 2;
     }
-    optional State state = 4;
+    optional State state = 4 [(state_field_option).option = EXCLUSIVE];
 }
 
 /*
@@ -3967,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];
 }
 
 /**
@@ -4093,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;
 }
 
 /*
@@ -5939,7 +5989,8 @@
     optional bool is_ongoing = 10;
 
     // Whether the bubble is produced by an app running in foreground.
-    optional bool is_foreground = 11;
+    // This is deprecated and the value should be ignored.
+    optional bool is_foreground = 11 [deprecated = true];
 }
 
 /**
@@ -7163,3 +7214,64 @@
     // It's required that len(time_millis) == len(frame_count)
     repeated int64 frame_counts = 2;
 }
+
+/**
+ * Information about camera facing and API level usage.
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/camera/CameraServiceProxy.java
+ */
+message CameraActionEvent {
+    // Camera session duration
+    optional int64 duration = 1;
+
+    // Camera API level used
+    optional int32 api_level = 2;
+
+    // Name of client package
+    optional string package_name = 3;
+
+    // Camera facing
+    enum Facing {
+        UNKNOWN = 0;
+        BACK = 1;
+        FRONT = 2;
+        EXTERNAL = 3;
+    }
+    optional Facing facing = 4;
+}
+
+/**
+ * Logs when a compatibility change is affecting an app.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/android/app/AppCompatCallbacks.java and
+ *   frameworks/base/services/core/java/com/android/server/compat/PlatformCompat.java
+ */
+message AppCompatibilityChangeReported {
+    // The UID of the app being affected by the compatibilty change.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // The ID of the change affecting the app.
+    optional int64 change_id = 2;
+
+    enum State {
+        UNKNOWN_STATE = 0;
+        ENABLED = 1;
+        DISABLED = 2;
+        LOGGED = 3;
+    }
+
+    // The state of the change - if logged from gating whether it was enabled or disabled, or just
+    // logged otherwise.
+    optional State state = 3;
+
+    enum Source {
+        UNKNOWN_SOURCE = 0;
+        APP_PROCESS = 1;
+        SYSTEM_SERVER = 2;
+    }
+
+    // Where it was logged from.
+    optional Source source = 4;
+
+}
diff --git a/cmds/statsd/src/condition/StateTracker.cpp b/cmds/statsd/src/condition/StateConditionTracker.cpp
similarity index 89%
rename from cmds/statsd/src/condition/StateTracker.cpp
rename to cmds/statsd/src/condition/StateConditionTracker.cpp
index 18c7178..7f3eedd 100644
--- a/cmds/statsd/src/condition/StateTracker.cpp
+++ b/cmds/statsd/src/condition/StateConditionTracker.cpp
@@ -16,7 +16,7 @@
 #define DEBUG false  // STOPSHIP if true
 #include "Log.h"
 
-#include "StateTracker.h"
+#include "StateConditionTracker.h"
 #include "guardrail/StatsdStats.h"
 
 namespace android {
@@ -27,7 +27,7 @@
 using std::unordered_set;
 using std::vector;
 
-StateTracker::StateTracker(const ConfigKey& key, const int64_t& id, const int index,
+StateConditionTracker::StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
                            const SimplePredicate& simplePredicate,
                            const unordered_map<int64_t, int>& trackerNameIndexMap,
                            const vector<Matcher> primaryKeys)
@@ -69,19 +69,19 @@
     mInitialized = true;
 }
 
-StateTracker::~StateTracker() {
-    VLOG("~StateTracker()");
+StateConditionTracker::~StateConditionTracker() {
+    VLOG("~StateConditionTracker()");
 }
 
-bool StateTracker::init(const vector<Predicate>& allConditionConfig,
+bool StateConditionTracker::init(const vector<Predicate>& allConditionConfig,
                         const vector<sp<ConditionTracker>>& allConditionTrackers,
                         const unordered_map<int64_t, int>& conditionIdIndexMap,
                         vector<bool>& stack) {
     return mInitialized;
 }
 
-void StateTracker::dumpState() {
-    VLOG("StateTracker %lld DUMP:", (long long)mConditionId);
+void StateConditionTracker::dumpState() {
+    VLOG("StateConditionTracker %lld DUMP:", (long long)mConditionId);
     for (const auto& value : mSlicedState) {
         VLOG("\t%s -> %s", value.first.toString().c_str(), value.second.toString().c_str());
     }
@@ -95,7 +95,7 @@
     }
 }
 
-bool StateTracker::hitGuardRail(const HashableDimensionKey& newKey) {
+bool StateConditionTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     if (mSlicedState.find(newKey) != mSlicedState.end()) {
         // if the condition is not sliced or the key is not new, we are good!
         return false;
@@ -114,7 +114,7 @@
     return false;
 }
 
-void StateTracker::evaluateCondition(const LogEvent& event,
+void StateConditionTracker::evaluateCondition(const LogEvent& event,
                                      const vector<MatchingState>& eventMatcherValues,
                                      const vector<sp<ConditionTracker>>& mAllConditions,
                                      vector<ConditionState>& conditionCache,
@@ -135,7 +135,7 @@
         return;
     }
 
-    VLOG("StateTracker evaluate event %s", event.ToString().c_str());
+    VLOG("StateConditionTracker evaluate event %s", event.ToString().c_str());
 
     // Primary key can exclusive fields must be simple fields. so there won't be more than
     // one keys matched.
@@ -151,7 +151,7 @@
     }
     hitGuardRail(primaryKey);
 
-    VLOG("StateTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
+    VLOG("StateConditionTracker: key %s state %s", primaryKey.toString().c_str(), state.toString().c_str());
 
     auto it = mSlicedState.find(primaryKey);
     if (it == mSlicedState.end()) {
@@ -176,7 +176,7 @@
     return;
 }
 
-void StateTracker::isConditionMet(
+void StateConditionTracker::isConditionMet(
         const ConditionKey& conditionParameters, const vector<sp<ConditionTracker>>& allConditions,
         const bool isPartialLink,
         vector<ConditionState>& conditionCache) const {
diff --git a/cmds/statsd/src/condition/StateTracker.h b/cmds/statsd/src/condition/StateConditionTracker.h
similarity index 89%
rename from cmds/statsd/src/condition/StateTracker.h
rename to cmds/statsd/src/condition/StateConditionTracker.h
index 5ae4441..0efe1fb 100644
--- a/cmds/statsd/src/condition/StateTracker.h
+++ b/cmds/statsd/src/condition/StateConditionTracker.h
@@ -25,14 +25,14 @@
 namespace os {
 namespace statsd {
 
-class StateTracker : public virtual ConditionTracker {
+class StateConditionTracker : public virtual ConditionTracker {
 public:
-    StateTracker(const ConfigKey& key, const int64_t& id, const int index,
+    StateConditionTracker(const ConfigKey& key, const int64_t& id, const int index,
                  const SimplePredicate& simplePredicate,
                  const std::unordered_map<int64_t, int>& trackerNameIndexMap,
                  const vector<Matcher> primaryKeys);
 
-    ~StateTracker();
+    ~StateConditionTracker();
 
     bool init(const std::vector<Predicate>& allConditionConfig,
               const std::vector<sp<ConditionTracker>>& allConditionTrackers,
@@ -46,8 +46,8 @@
                            std::vector<bool>& changedCache) override;
 
     /**
-     * Note: dimensionFields will be ignored in StateTracker, because we demand metrics
-     * must take the entire dimension fields from StateTracker. This is to make implementation
+     * Note: dimensionFields will be ignored in StateConditionTracker, because we demand metrics
+     * must take the entire dimension fields from StateConditionTracker. This is to make implementation
      * simple and efficient.
      *
      * For example: wakelock duration by uid process states:
@@ -109,7 +109,7 @@
     // maps from [primary_key] to [primary_key, exclusive_state].
     std::unordered_map<HashableDimensionKey, HashableDimensionKey> mSlicedState;
 
-    FRIEND_TEST(StateTrackerTest, TestStateChange);
+    FRIEND_TEST(StateConditionTrackerTest, TestStateChange);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 475f18a..f69e2d0 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -153,6 +153,9 @@
          {.additiveFields = {3},
           .puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+        // process_memory_snapshot
+        {android::util::PROCESS_MEMORY_SNAPSHOT,
+         {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
         // system_ion_heap_size
         {android::util::SYSTEM_ION_HEAP_SIZE,
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 10ac4a1..476fae3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -358,9 +358,10 @@
 
 bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher,
                    const LogEvent& event) {
-    if (simpleMatcher.field_value_matcher_size() <= 0) {
-        return event.GetTagId() == simpleMatcher.atom_id();
+    if (event.GetTagId() != simpleMatcher.atom_id()) {
+        return false;
     }
+
     for (const auto& matcher : simpleMatcher.field_value_matcher()) {
         if (!matchesSimple(uidMap, matcher, event.getValues(), 0, event.getValues().size(), 0)) {
             return false;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 94f833b..fdbdc83 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -83,9 +83,9 @@
           mCurrentBucketStartTimeNs(timeBaseNs),
           mCurrentBucketNum(0),
           mCondition(initialCondition(conditionIndex)),
+          mConditionTrackerIndex(conditionIndex),
           mConditionSliced(false),
           mWizard(wizard),
-          mConditionTrackerIndex(conditionIndex),
           mContainANYPositionInDimensionsInWhat(false),
           mSliceByPositionALL(false),
           mHasLinksToAllConditionDimensionsInTracker(false),
@@ -167,11 +167,6 @@
         return clearPastBucketsLocked(dumpTimeNs);
     }
 
-    void dumpStates(FILE* out, bool verbose) const {
-        std::lock_guard<std::mutex> lock(mMutex);
-        dumpStatesLocked(out, verbose);
-    }
-
     // Returns the memory in bytes currently used to store this metric's data. Does not change
     // state.
     size_t byteSize() const {
@@ -179,34 +174,9 @@
         return byteSizeLocked();
     }
 
-    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
-    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
-                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+    void dumpStates(FILE* out, bool verbose) const {
         std::lock_guard<std::mutex> lock(mMutex);
-        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
-        if (anomalyTracker != nullptr) {
-            mAnomalyTrackers.push_back(anomalyTracker);
-        }
-        return anomalyTracker;
-    }
-
-    int64_t getBuckeSizeInNs() const {
-        std::lock_guard<std::mutex> lock(mMutex);
-        return mBucketSizeNs;
-    }
-
-    // Only needed for unit-testing to override guardrail.
-    void setBucketSize(int64_t bucketSize) {
-        mBucketSizeNs = bucketSize;
-    }
-
-    inline const int64_t& getMetricId() const {
-        return mMetricId;
-    }
-
-    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
-        std::lock_guard<std::mutex> lock(mMutex);
-        loadActiveMetricLocked(activeMetric, currentTimeNs);
+        dumpStatesLocked(out, verbose);
     }
 
     // Let MetricProducer drop in-memory data to save memory.
@@ -218,9 +188,14 @@
         dropDataLocked(dropTimeNs);
     }
 
-    // For test only.
-    inline int64_t getCurrentBucketNum() const {
-        return mCurrentBucketNum;
+    void prepareFirstBucket() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        prepareFirstBucketLocked();
+    }
+
+    void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        loadActiveMetricLocked(activeMetric, currentTimeNs);
     }
 
     void activate(int activationTrackerIndex, int64_t elapsedTimestampNs) {
@@ -238,44 +213,41 @@
         return isActiveLocked();
     }
 
+    void flushIfExpire(int64_t elapsedTimestampNs);
+
     void addActivation(int activationTrackerIndex, const ActivationType& activationType,
             int64_t ttl_seconds, int deactivationTrackerIndex = -1);
 
-    void prepareFirstBucket() {
-        std::lock_guard<std::mutex> lock(mMutex);
-        prepareFirstBucketLocked();
-    }
-
-    void flushIfExpire(int64_t elapsedTimestampNs);
-
     void writeActiveMetricToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
-protected:
-    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
-    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
-                                                  const int64_t eventTime) = 0;
-    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
-                                    const bool include_current_partial_bucket,
-                                    const bool erase_data,
-                                    const DumpLatency dumpLatency,
-                                    std::set<string> *str_set,
-                                    android::util::ProtoOutputStream* protoOutput) = 0;
-    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
-    virtual size_t byteSizeLocked() const = 0;
-    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
 
-    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
-
-    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
-    void cancelEventActivationLocked(int deactivationTrackerIndex);
-
-    inline bool isActiveLocked() const {
-        return mIsActive;
+    // Start: getters/setters
+    inline const int64_t& getMetricId() const {
+        return mMetricId;
     }
 
-    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    // For test only.
+    inline int64_t getCurrentBucketNum() const {
+        return mCurrentBucketNum;
+    }
 
-    virtual void prepareFirstBucketLocked() {};
+    int64_t getBucketSizeInNs() const {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mBucketSizeNs;
+    }
+
+    /* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
+    virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
+                                                 const sp<AlarmMonitor>& anomalyAlarmMonitor) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        sp<AnomalyTracker> anomalyTracker = new AnomalyTracker(alert, mConfigKey);
+        if (anomalyTracker != nullptr) {
+            mAnomalyTrackers.push_back(anomalyTracker);
+        }
+        return anomalyTracker;
+    }
+    // End: getters/setters
+protected:
     /**
      * Flushes the current bucket if the eventTime is after the current bucket's end time. This will
        also flush the current partial bucket in memory.
@@ -283,14 +255,6 @@
     virtual void flushIfNeededLocked(const int64_t& eventTime){};
 
     /**
-     * Flushes all the data including the current partial bucket.
-     */
-    virtual void flushLocked(const int64_t& eventTimeNs) {
-        flushIfNeededLocked(eventTimeNs);
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-    };
-
-    /**
      * For metrics that aggregate (ie, every metric producer except for EventMetricProducer),
      * we need to be able to flush the current buckets on demand (ie, end the current bucket and
      * start new bucket). If this function is called when eventTimeNs is greater than the current
@@ -303,62 +267,13 @@
     virtual void flushCurrentBucketLocked(const int64_t& eventTimeNs,
                                           const int64_t& nextBucketStartTimeNs) {};
 
-    virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
-        if (!mIsActive) {
-            flushLocked(eventTimeNs);
-        }
-    }
-
-    // Convenience to compute the current bucket's end time, which is always aligned with the
-    // start time of the metric.
-    int64_t getCurrentBucketEndTimeNs() const {
-        return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
-    }
-
-    int64_t getBucketNumFromEndTimeNs(const int64_t endNs) {
-        return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
-    }
-
-    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
-
-    const int64_t mMetricId;
-
-    const ConfigKey mConfigKey;
-
-    // The time when this metric producer was first created. The end time for the current bucket
-    // can be computed from this based on mCurrentBucketNum.
-    int64_t mTimeBaseNs;
-
-    // Start time may not be aligned with the start of statsd if there is an app upgrade in the
-    // middle of a bucket.
-    int64_t mCurrentBucketStartTimeNs;
-
-    // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
-    // report.
-    int64_t mCurrentBucketNum;
-
-    int64_t mBucketSizeNs;
-
-    ConditionState mCondition;
-
-    bool mConditionSliced;
-
-    sp<ConditionWizard> mWizard;
-
-    int mConditionTrackerIndex;
-
-    vector<Matcher> mDimensionsInWhat;       // The dimensions_in_what defined in statsd_config
-
-    bool mContainANYPositionInDimensionsInWhat;
-    bool mSliceByPositionALL;
-
-    // True iff the metric to condition links cover all dimension fields in the condition tracker.
-    // This field is always false for combinational condition trackers.
-    bool mHasLinksToAllConditionDimensionsInTracker;
-
-    std::vector<Metric2Condition> mMetric2ConditionLinks;
-
-    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
+    /**
+     * Flushes all the data including the current partial bucket.
+     */
+    virtual void flushLocked(const int64_t& eventTimeNs) {
+        flushIfNeededLocked(eventTimeNs);
+        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
 
     /*
      * Individual metrics can implement their own business logic here. All pre-processing is done.
@@ -382,6 +297,85 @@
 
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     virtual void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event);
+    virtual void onConditionChangedLocked(const bool condition, const int64_t eventTime) = 0;
+    virtual void onSlicedConditionMayChangeLocked(bool overallCondition,
+                                                  const int64_t eventTime) = 0;
+    virtual void onDumpReportLocked(const int64_t dumpTimeNs,
+                                    const bool include_current_partial_bucket,
+                                    const bool erase_data,
+                                    const DumpLatency dumpLatency,
+                                    std::set<string> *str_set,
+                                    android::util::ProtoOutputStream* protoOutput) = 0;
+    virtual void clearPastBucketsLocked(const int64_t dumpTimeNs) = 0;
+    virtual size_t byteSizeLocked() const = 0;
+    virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
+    virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
+    virtual void prepareFirstBucketLocked() {};
+    void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
+    void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
+    void cancelEventActivationLocked(int deactivationTrackerIndex);
+
+    bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
+
+    virtual void onActiveStateChangedLocked(const int64_t& eventTimeNs) {
+        if (!mIsActive) {
+            flushLocked(eventTimeNs);
+        }
+    }
+
+    inline bool isActiveLocked() const {
+        return mIsActive;
+    }
+
+    // Convenience to compute the current bucket's end time, which is always aligned with the
+    // start time of the metric.
+    int64_t getCurrentBucketEndTimeNs() const {
+        return mTimeBaseNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
+    }
+
+    int64_t getBucketNumFromEndTimeNs(const int64_t endNs) {
+        return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
+    }
+
+    const int64_t mMetricId;
+
+    const ConfigKey mConfigKey;
+
+    // The time when this metric producer was first created. The end time for the current bucket
+    // can be computed from this based on mCurrentBucketNum.
+    int64_t mTimeBaseNs;
+
+    // Start time may not be aligned with the start of statsd if there is an app upgrade in the
+    // middle of a bucket.
+    int64_t mCurrentBucketStartTimeNs;
+
+    // Used by anomaly detector to track which bucket we are in. This is not sent with the produced
+    // report.
+    int64_t mCurrentBucketNum;
+
+    int64_t mBucketSizeNs;
+
+    ConditionState mCondition;
+
+    int mConditionTrackerIndex;
+
+    bool mConditionSliced;
+
+    sp<ConditionWizard> mWizard;
+
+    bool mContainANYPositionInDimensionsInWhat;
+
+    bool mSliceByPositionALL;
+
+    vector<Matcher> mDimensionsInWhat;  // The dimensions_in_what defined in statsd_config
+
+    // True iff the metric to condition links cover all dimension fields in the condition tracker.
+    // This field is always false for combinational condition trackers.
+    bool mHasLinksToAllConditionDimensionsInTracker;
+
+    std::vector<Metric2Condition> mMetric2ConditionLinks;
+
+    std::vector<sp<AnomalyTracker>> mAnomalyTrackers;
 
     mutable std::mutex mMutex;
 
@@ -397,6 +391,7 @@
         ActivationState state;
         const ActivationType activationType;
     };
+
     // When the metric producer has multiple activations, these activations are ORed to determine
     // whether the metric producer is ready to generate metrics.
     std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index dd32c08..40484f4 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -21,7 +21,7 @@
 
 #include "../condition/CombinationConditionTracker.h"
 #include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateTracker.h"
+#include "../condition/StateConditionTracker.h"
 #include "../external/StatsPullerManager.h"
 #include "../matchers/CombinationLogMatchingTracker.h"
 #include "../matchers/SimpleLogMatchingTracker.h"
@@ -35,6 +35,8 @@
 #include "stats_util.h"
 #include "statslog.h"
 
+#include <inttypes.h>
+
 using std::set;
 using std::string;
 using std::unordered_map;
@@ -182,13 +184,13 @@
 }
 
 /**
- * A StateTracker is built from a SimplePredicate which has only "start", and no "stop"
+ * A StateConditionTracker is built from a SimplePredicate which has only "start", and no "stop"
  * or "stop_all". The start must be an atom matcher that matches a state atom. It must
  * have dimension, the dimension must be the state atom's primary fields plus exclusive state
- * field. For example, the StateTracker is used in tracking UidProcessState and ScreenState.
+ * field. For example, the StateConditionTracker is used in tracking UidProcessState and ScreenState.
  *
  */
-bool isStateTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
+bool isStateConditionTracker(const SimplePredicate& simplePredicate, vector<Matcher>* primaryKeys) {
     // 1. must not have "stop". must have "dimension"
     if (!simplePredicate.has_stop() && simplePredicate.has_dimensions()) {
         auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(
@@ -240,8 +242,8 @@
         switch (condition.contents_case()) {
             case Predicate::ContentsCase::kSimplePredicate: {
                 vector<Matcher> primaryKeys;
-                if (isStateTracker(condition.simple_predicate(), &primaryKeys)) {
-                    allConditionTrackers.push_back(new StateTracker(key, condition.id(), index,
+                if (isStateConditionTracker(condition.simple_predicate(), &primaryKeys)) {
+                    allConditionTrackers.push_back(new StateConditionTracker(key, condition.id(), index,
                                                                     condition.simple_predicate(),
                                                                     logTrackerMap, primaryKeys));
                 } else {
@@ -593,7 +595,7 @@
     for (int i = 0; i < config.no_report_metric_size(); ++i) {
         const auto no_report_metric = config.no_report_metric(i);
         if (metricMap.find(no_report_metric) == metricMap.end()) {
-            ALOGW("no_report_metric %lld not exist", no_report_metric);
+            ALOGW("no_report_metric %" PRId64 " not exist", no_report_metric);
             return false;
         }
         noReportMetricIds.insert(no_report_metric);
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 028231f..3704969 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -113,7 +113,7 @@
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds);
 
-bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
+bool isStateConditionTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/state/StateListener.h b/cmds/statsd/src/state/StateListener.h
new file mode 100644
index 0000000..a31690a
--- /dev/null
+++ b/cmds/statsd/src/state/StateListener.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "HashableDimensionKey.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateListener : public virtual RefBase {
+public:
+    StateListener(){};
+
+    virtual ~StateListener(){};
+
+    /**
+     * Interface for handling a state change.
+     *
+     * The old and new state values map to the original state values.
+     * StateTrackers only track the original state values and are unaware
+     * of higher-level state groups. MetricProducers hold information on
+     * state groups and are responsible for mapping original state values to
+     * the correct state group.
+     *
+     * [atomId]: The id of the state atom
+     * [primaryKey]: The primary field values of the state atom
+     * [oldState]: Previous state value before state change
+     * [newState]: Current state value after state change
+     */
+    virtual void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+                                int newState) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
new file mode 100644
index 0000000..a3059c5
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "StateManager.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateManager& StateManager::getInstance() {
+    static StateManager sStateManager;
+    return sStateManager;
+}
+
+void StateManager::onLogEvent(const LogEvent& event) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(event.GetTagId()) != mStateTrackers.end()) {
+        mStateTrackers[event.GetTagId()]->onLogEvent(event);
+    }
+}
+
+bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    // Check if state tracker already exists
+    if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+        // Create a new state tracker iff atom is a state atom
+        auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+        if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
+            mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+        } else {
+            ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
+                  stateAtomId);
+            return false;
+        }
+    }
+    mStateTrackers[stateAtomId]->registerListener(listener);
+    return true;
+}
+
+void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+    std::unique_lock<std::mutex> lock(mMutex);
+
+    // Hold the sp<> until the lock is released so that ~StateTracker() is
+    // not called while the lock is held.
+    sp<StateTracker> toRemove;
+
+    // Unregister listener from correct StateTracker
+    auto it = mStateTrackers.find(stateAtomId);
+    if (it != mStateTrackers.end()) {
+        it->second->unregisterListener(listener);
+
+        // Remove the StateTracker if it has no listeners
+        if (it->second->getListenersCount() == 0) {
+            toRemove = it->second;
+            mStateTrackers.erase(it);
+        }
+    } else {
+        ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
+              stateAtomId);
+    }
+    lock.unlock();
+}
+
+int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+        return mStateTrackers[stateAtomId]->getState(key);
+    }
+
+    return StateTracker::kStateUnknown;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
new file mode 100644
index 0000000..ce60f14
--- /dev/null
+++ b/cmds/statsd/src/state/StateManager.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+//#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+
+#include "state/StateListener.h"
+#include "state/StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateManager : public virtual RefBase {
+public:
+    StateManager(){};
+
+    ~StateManager(){};
+
+    // Returns a pointer to the single, shared StateManager object.
+    static StateManager& getInstance();
+
+    // Notifies the correct StateTracker of an event.
+    void onLogEvent(const LogEvent& event);
+
+    // Returns true if stateAtomId is the id of a state atom and notifies the
+    // correct StateTracker to register the listener. If the correct
+    // StateTracker does not exist, a new StateTracker is created.
+    bool registerListener(int stateAtomId, wp<StateListener> listener);
+
+    // Notifies the correct StateTracker to unregister a listener
+    // and removes the tracker if it no longer has any listeners.
+    void unregisterListener(int stateAtomId, wp<StateListener> listener);
+
+    // Queries the correct StateTracker for the state that is mapped to the given
+    // query key.
+    // If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
+    int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+
+    inline int getStateTrackersCount() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mStateTrackers.size();
+    }
+
+    inline int getListenersCount(int stateAtomId) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
+            return mStateTrackers[stateAtomId]->getListenersCount();
+        }
+        return -1;
+    }
+
+private:
+  mutable std::mutex mMutex;
+
+  // Maps state atom ids to StateTrackers
+  std::unordered_map<int, sp<StateTracker>> mStateTrackers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
new file mode 100644
index 0000000..5a91950
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "stats_util.h"
+
+#include "StateTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+StateTracker::StateTracker(const int atomId,
+                           const util::StateAtomFieldOptions& stateAtomInfo)
+  : mAtomId(atomId),
+    mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
+    // create matcher for each primary field
+    // TODO(tsaichristine): handle when primary field is first uid in chain
+    for (const auto& primary : stateAtomInfo.primaryFields) {
+        Matcher matcher = getSimpleMatcher(atomId, primary);
+        mPrimaryFields.push_back(matcher);
+    }
+
+    // TODO(tsaichristine): set default state, reset state, and nesting
+}
+
+void StateTracker::onLogEvent(const LogEvent& event) {
+    // parse event for primary field values i.e. primary key
+    HashableDimensionKey primaryKey;
+    if (mPrimaryFields.size() > 0) {
+        if (!filterValues(mPrimaryFields, event.getValues(), &primaryKey) ||
+            primaryKey.getValues().size() != mPrimaryFields.size()) {
+            ALOGE("StateTracker error extracting primary key from log event.");
+            handleReset();
+            return;
+        }
+    } else {
+        // atom has no primary fields
+        primaryKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    // parse event for state value
+    Value state;
+    int32_t stateValue;
+    if (!filterValues(mStateField, event.getValues(), &state) || state.getType() != INT) {
+        ALOGE("StateTracker error extracting state from log event. Type: %d", state.getType());
+        handlePartialReset(primaryKey);
+        return;
+    }
+    stateValue = state.int_value;
+
+    if (stateValue == mResetState) {
+        VLOG("StateTracker Reset state: %s", state.toString().c_str());
+        handleReset();
+    }
+
+    // track and update state
+    int32_t oldState = 0;
+    int32_t newState = 0;
+    updateState(primaryKey, stateValue, &oldState, &newState);
+
+    // notify all listeners if state has changed
+    if (oldState != newState) {
+        VLOG("StateTracker updated state");
+        for (auto listener : mListeners) {
+            auto sListener = listener.promote();  // safe access to wp<>
+            if (sListener != nullptr) {
+                sListener->onStateChanged(mAtomId, primaryKey, oldState, newState);
+            }
+        }
+    } else {
+        VLOG("StateTracker NO updated state");
+    }
+}
+
+void StateTracker::registerListener(wp<StateListener> listener) {
+    mListeners.insert(listener);
+}
+
+void StateTracker::unregisterListener(wp<StateListener> listener) {
+    mListeners.erase(listener);
+}
+
+int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+    if (queryKey.getValues().size() == mPrimaryFields.size()) {
+        auto it = mStateMap.find(queryKey);
+        if (it != mStateMap.end()) {
+            return it->second.state;
+        }
+    } else if (queryKey.getValues().size() > mPrimaryFields.size()) {
+        ALOGE("StateTracker query key size > primary key size is illegal");
+    } else {
+        ALOGE("StateTracker query key size < primary key size is not supported");
+    }
+    return mDefaultState;
+}
+
+void StateTracker::handleReset() {
+    VLOG("StateTracker handle reset");
+    for (const auto pair : mStateMap) {
+        for (auto l : mListeners) {
+            auto sl = l.promote();
+            if (sl != nullptr) {
+                sl->onStateChanged(mAtomId, pair.first, pair.second.state, mDefaultState);
+            }
+        }
+    }
+    mStateMap.clear();
+}
+
+void StateTracker::handlePartialReset(const HashableDimensionKey& primaryKey) {
+    VLOG("StateTracker handle partial reset");
+    if (mStateMap.find(primaryKey) != mStateMap.end()) {
+        mStateMap.erase(primaryKey);
+    }
+}
+
+void StateTracker::updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                               int32_t* oldState, int32_t* newState) {
+    // get old state (either current state in map or default state)
+    auto it = mStateMap.find(primaryKey);
+    if (it != mStateMap.end()) {
+        *oldState = it->second.state;
+    } else {
+        *oldState = mDefaultState;
+    }
+
+    // update state map
+    if (eventState == mDefaultState) {
+        // remove (key, state) pair if state returns to default state
+        VLOG("\t StateTracker changed to default state")
+        mStateMap.erase(primaryKey);
+    } else {
+        mStateMap[primaryKey].state = eventState;
+        mStateMap[primaryKey].count = 1;
+    }
+    *newState = eventState;
+
+    // TODO(tsaichristine): support atoms with nested counting
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
new file mode 100644
index 0000000..f22706c
--- /dev/null
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <statslog.h>
+#include <utils/RefBase.h>
+#include "HashableDimensionKey.h"
+#include "logd/LogEvent.h"
+
+#include "state/StateListener.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StateTracker : public virtual RefBase {
+public:
+    StateTracker(const int atomId, const util::StateAtomFieldOptions& stateAtomInfo);
+
+    virtual ~StateTracker(){};
+
+    // Updates state map and notifies all listeners if a state change occurs.
+    // Checks if a state change has occurred by getting the state value from
+    // the log event and comparing the old and new states.
+    void onLogEvent(const LogEvent& event);
+
+    // Adds new listeners to set of StateListeners. If a listener is already
+    // registered, it is ignored.
+    void registerListener(wp<StateListener> listener);
+
+    void unregisterListener(wp<StateListener> listener);
+
+    // Returns the state value mapped to the given query key.
+    // If the key isn't mapped to a state or the key size doesn't match the
+    // primary key size, the default state is returned.
+    int getState(const HashableDimensionKey& queryKey) const;
+
+    inline int getListenersCount() const {
+        return mListeners.size();
+    }
+
+    const static int kStateUnknown = -1;
+
+private:
+    struct StateValueInfo {
+        int32_t state;  // state value
+        int count;      // nested count (only used for binary states)
+    };
+
+    const int32_t mAtomId;  // id of the state atom being tracked
+
+    Matcher mStateField;  // matches the atom's exclusive state field
+
+    std::vector<Matcher> mPrimaryFields;  // matches the atom's primary fields
+
+    int32_t mDefaultState = kStateUnknown;
+
+    int32_t mResetState;
+
+    // Maps primary key to state value info
+    std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
+
+    // Set of all StateListeners (objects listening for state changes)
+    std::set<wp<StateListener>> mListeners;
+
+    // Reset all state values in map to default state
+    void handleReset();
+
+    // Reset only the state value mapped to primary key to default state
+    void handlePartialReset(const HashableDimensionKey& primaryKey);
+
+    // Update the StateMap based on the received state value.
+    // Store the old and new states.
+    void updateState(const HashableDimensionKey& primaryKey, const int32_t eventState,
+                     int32_t* oldState, int32_t* newState);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 70f0f6f..441d3c8 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -330,6 +330,7 @@
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
 
     // Tag found in kAtomsWithUidField and has matching uid
+    simpleMatcher->set_atom_id(TAG_ID_2);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     // Tag found in kAtomsWithUidField but has non-matching uid
diff --git a/cmds/statsd/tests/condition/StateTracker_test.cpp b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
similarity index 95%
rename from cmds/statsd/tests/condition/StateTracker_test.cpp
rename to cmds/statsd/tests/condition/StateConditionTracker_test.cpp
index 9a66254..fbf6efd 100644
--- a/cmds/statsd/tests/condition/StateTracker_test.cpp
+++ b/cmds/statsd/tests/condition/StateConditionTracker_test.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/condition/StateTracker.h"
+#include "src/condition/StateConditionTracker.h"
 #include "tests/statsd_test_util.h"
 
 #include <gmock/gmock.h>
@@ -50,7 +50,7 @@
     event->init();
 }
 
-TEST(StateTrackerTest, TestStateChange) {
+TEST(StateConditionTrackerTest, TestStateChange) {
     int uid1 = 111;
     int uid2 = 222;
 
@@ -60,7 +60,7 @@
     trackerNameIndexMap[StringToId("UidProcState")] = 0;
     vector<Matcher> primaryFields;
     primaryFields.push_back(getSimpleMatcher(kUidProcTag, 1));
-    StateTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
+    StateConditionTracker tracker(ConfigKey(12, 123), 123, 0, getUidProcStatePredicate(),
                          trackerNameIndexMap, primaryFields);
 
     LogEvent event(kUidProcTag, 0 /*timestamp*/);
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
new file mode 100644
index 0000000..c89ffea
--- /dev/null
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+#include "state/StateManager.h"
+#include "state/StateTracker.h"
+#include "state/StateListener.h"
+
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Mock StateListener class for testing.
+ * Stores primary key and state pairs.
+ */
+class TestStateListener : public virtual StateListener {
+public:
+    TestStateListener(){};
+
+    virtual ~TestStateListener(){};
+
+    struct Update {
+        Update(const HashableDimensionKey& key, int state) : mKey(key), mState(state){};
+        HashableDimensionKey mKey;
+        int mState;
+    };
+
+    std::vector<Update> updates;
+
+    void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+                        int newState) {
+        updates.emplace_back(primaryKey, newState);
+    }
+};
+
+// START: build event functions.
+// State with no primary fields - ScreenStateChanged
+std::shared_ptr<LogEvent> buildScreenEvent(int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::SCREEN_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with one primary field - UidProcessStateChanged
+std::shared_ptr<LogEvent> buildUidProcessEvent(int uid, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::UID_PROCESS_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+// State with multiple primary fields - OverlayStateChanged
+std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write(true);  // using_alert_window
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+
+std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
+    std::shared_ptr<LogEvent> event =
+            std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+    event->write((int32_t)uid);
+    event->write(packageName);
+    event->write((int32_t)state);
+    event->init();
+    return event;
+}
+// END: build event functions.
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
+}
+// END: get primary key functions
+
+TEST(StateListenerTest, TestStateListenerWeakPointer) {
+    sp<TestStateListener> listener = new TestStateListener();
+    wp<TestStateListener> wListener = listener;
+    listener = nullptr;  // let go of listener
+    EXPECT_TRUE(wListener.promote() == nullptr);
+}
+
+TEST(StateManagerTest, TestStateManagerGetInstance) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager& mgr = StateManager::getInstance();
+
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+}
+
+/**
+ * Test registering listeners to StateTrackers
+ *
+ * - StateManager will create a new StateTracker if it doesn't already exist
+ * and then register the listener to the StateTracker.
+ * - If a listener is already registered to a StateTracker, it is not added again.
+ * - StateTrackers are only created for atoms that are state atoms.
+ */
+TEST(StateTrackerTest, TestRegisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Register listener to non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register already registered listener to existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Register listener to non-state atom
+    mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+}
+
+/**
+ * Test unregistering listeners from StateTrackers
+ *
+ * - StateManager will unregister listeners from a StateTracker only if the
+ * StateTracker exists and the listener is registered to the StateTracker.
+ * - Once all listeners are removed from a StateTracker, the StateTracker
+ * is also removed.
+ */
+TEST(StateTrackerTest, TestUnregisterListener) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    StateManager mgr;
+
+    // Unregister listener from non-existing StateTracker
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister non-registered listener from existing StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister second-to-last listener from StateTracker
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    EXPECT_EQ(1, mgr.getStateTrackersCount());
+    EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+    // Unregister last listener from StateTracker
+    mgr.unregisterListener(android::util::SCREEN_STATE_CHANGED, listener2);
+    EXPECT_EQ(0, mgr.getStateTrackersCount());
+    EXPECT_EQ(-1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states without primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(DEFAULT_DIMENSION_KEY, listener1->updates[0].mKey);
+    EXPECT_EQ(2, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with primary keys.
+ */
+TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  //  state value: 1002
+    mgr.onLogEvent(*event);
+
+    // check listener was updated
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1002, listener1->updates[0].mState);
+
+    // check StateTracker was updated by querying for state
+    HashableDimensionKey queryKey;
+    getUidProcessKey(1000, &queryKey);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(1, listener1->updates.size());
+    EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+    EXPECT_EQ(1, listener1->updates[0].mState);
+}
+
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged
+ * when there is an error extracting state from log event. Listener is not
+ * updated of state change.
+ */
+TEST(StateTrackerTest, TestStateChangeEventError) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
+
+    // log event
+    std::shared_ptr<LogEvent> event =
+            buildIncorrectOverlayEvent(1000, "package1", 1);  // state: ENTERED
+    mgr.onLogEvent(*event);
+
+    // check listener update
+    EXPECT_EQ(0, listener1->updates.size());
+}
+
+TEST(StateTrackerTest, TestStateQuery) {
+    sp<TestStateListener> listener1 = new TestStateListener();
+    sp<TestStateListener> listener2 = new TestStateListener();
+    sp<TestStateListener> listener3 = new TestStateListener();
+    StateManager mgr;
+    mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+    mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
+    mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+
+    std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
+            1000,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE);  // state value: 1003
+    std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
+            1002,
+            android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT);  // state value: 1000
+    std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
+            1001,
+            android::app::ProcessStateEnum::PROCESS_STATE_TOP);  // state value: 1002
+    std::shared_ptr<LogEvent> event5 =
+            buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);  // state value:
+    std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
+    std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+
+    mgr.onLogEvent(*event1);
+    mgr.onLogEvent(*event2);
+    mgr.onLogEvent(*event3);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event5);
+    mgr.onLogEvent(*event6);
+    mgr.onLogEvent(*event7);
+
+    // Query for UidProcessState of uid 1001
+    HashableDimensionKey queryKey1;
+    getUidProcessKey(1001, &queryKey1);
+    EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for UidProcessState of uid 1004 - not in state map
+    HashableDimensionKey queryKey2;
+    getUidProcessKey(1004, &queryKey2);
+    EXPECT_EQ(-1,
+              mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2));  // default state
+
+    // Query for UidProcessState of uid 1001 - after change in state
+    mgr.onLogEvent(*event4);
+    EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+
+    // Query for ScreenState
+    EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+
+    // Query for OverlayState of uid 1000, package name "package2"
+    HashableDimensionKey queryKey3;
+    getOverlayKey(1000, "package2", &queryKey3);
+    EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 2ac8409..652669c 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43765,3 +43765,1725 @@
 HSPLjava/util/Arrays$ArrayList;-><init>([Ljava/lang/Object;)V
 HSPLjava/util/regex/Matcher;-><init>(Ljava/util/regex/Pattern;Ljava/lang/CharSequence;)V
 HSPLjava/util/Map;->getOrDefault(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+[[F
+[[J
+Landroid/accessibilityservice/IAccessibilityServiceClient$Stub;
+Landroid/accessibilityservice/IAccessibilityServiceClient$Stub$Proxy;
+[Landroid/accounts/Account;
+Landroid/accounts/AccountManager$17;
+Landroid/accounts/AccountManager$3;
+[Landroid/accounts/AuthenticatorDescription;
+Landroid/accounts/IAccountAuthenticator;
+Landroid/accounts/IAccountAuthenticator$Stub;
+Landroid/accounts/IAccountAuthenticator$Stub$Proxy;
+Landroid/accounts/IAccountManagerResponse$Stub$Proxy;
+Landroid/animation/AnimationHandler$2;
+[Landroid/animation/Animator;
+Landroid/animation/BidirectionalTypeConverter;
+[Landroid/animation/Keyframe;
+[Landroid/animation/Keyframe$FloatKeyframe;
+[Landroid/animation/Keyframe$IntKeyframe;
+[Landroid/animation/Keyframe$ObjectKeyframe;
+Landroid/animation/LayoutTransition$3;
+Landroid/animation/PathKeyframes$3;
+Landroid/animation/PathKeyframes$4;
+[Landroid/animation/PropertyValuesHolder;
+Landroid/animation/PropertyValuesHolder$1;
+[Landroid/apex/ApexInfo;
+Landroid/apex/ApexInfo;
+Landroid/app/-$$Lambda$ActivityThread$A4ykhsPb8qV3ffTqpQDklHSMDJ0;
+Landroid/app/-$$Lambda$ActivityThread$Wg40iAoNYFxps_KmrqtgptTB054;
+Landroid/app/-$$Lambda$ActivityTransitionState$yioLR6wQWjZ9DcWK5bibElIbsXc;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$DkVcBvqB32SMHlxw0sWQPh3GL1A;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$HUOLFYs8TiaQIOXcrq6JzjxA6gs;
+Landroid/app/-$$Lambda$AppOpsManager$HistoricalOp$Vs6pDL0wjOBTquwNnreWVbPQrn4;
+Landroid/app/-$$Lambda$FragmentTransition$jurn0WXuKw3bRQ_2d5zCWdeZWuI;
+Landroid/app/-$$Lambda$WallpaperManager$Globals$1AcnQUORvPlCjJoNqdxfQT4o4Nw;
+Landroid/app/-$$Lambda$WallpaperManager$Globals$2yG7V1sbMECCnlFTLyjKWKqNoYI;
+Landroid/app/Activity$1;
+Landroid/app/Activity$ManagedCursor;
+Landroid/app/Activity$ManagedDialog;
+Landroid/app/ActivityManager$TaskSnapshot;
+Landroid/app/ActivityTransitionState$1;
+Landroid/app/admin/DevicePolicyCache$EmptyDevicePolicyCache;
+Landroid/app/admin/DevicePolicyEventLogger;
+Landroid/app/admin/IDeviceAdminService;
+Landroid/app/admin/IDeviceAdminService$Stub$Proxy;
+Landroid/app/admin/StartInstallingUpdateCallback$Stub$Proxy;
+Landroid/app/AppCompatCallbacks;
+Landroid/app/ApplicationErrorReport;
+Landroid/app/ApplicationLoaders$CachedClassLoader;
+Landroid/app/ApplicationPackageManager$MoveCallbackDelegate;
+Landroid/app/AppOpsManager$1;
+Landroid/app/AppOpsManager$3;
+[Landroid/app/assist/AssistStructure$ViewNode;
+Landroid/app/AutomaticZenRule;
+[Landroid/app/BackStackState;
+Landroid/app/BackStackState;
+Landroid/app/backup/BlobBackupHelper;
+Landroid/app/backup/IBackupCallback$Stub;
+Landroid/app/backup/IBackupCallback$Stub$Proxy;
+Landroid/app/backup/IBackupManagerMonitor$Stub;
+Landroid/app/backup/IBackupManagerMonitor$Stub$Proxy;
+Landroid/app/backup/IBackupObserver$Stub;
+Landroid/app/backup/IBackupObserver$Stub$Proxy;
+Landroid/app/backup/IFullBackupRestoreObserver$Stub;
+Landroid/app/backup/IFullBackupRestoreObserver$Stub$Proxy;
+Landroid/app/backup/ISelectBackupTransportCallback$Stub;
+Landroid/app/backup/ISelectBackupTransportCallback$Stub$Proxy;
+Landroid/app/DownloadManager$CursorTranslator;
+Landroid/app/EnterTransitionCoordinator;
+Landroid/app/ExitTransitionCoordinator;
+Landroid/app/Fragment$InstantiationException;
+Landroid/app/FragmentManager$FragmentLifecycleCallbacks;
+Landroid/app/FragmentManager$OnBackStackChangedListener;
+Landroid/app/FragmentManagerImpl$2;
+Landroid/app/FragmentManagerImpl$AnimateOnHWLayerIfNeededListener;
+Landroid/app/FragmentManagerImpl$StartEnterTransitionListener;
+Landroid/app/FragmentManagerNonConfig;
+[Landroid/app/FragmentState;
+Landroid/app/FragmentTransition$2;
+Landroid/app/FragmentTransition$5;
+Landroid/app/IActivityController$Stub;
+Landroid/app/IActivityController$Stub$Proxy;
+Landroid/app/IAlarmCompleteListener$Stub$Proxy;
+Landroid/app/IAlarmListener$Stub$Proxy;
+Landroid/app/IAssistDataReceiver;
+Landroid/app/IAssistDataReceiver$Stub;
+Landroid/app/IAssistDataReceiver$Stub$Proxy;
+Landroid/app/IBackupAgent$Stub$Proxy;
+Landroid/app/IInstantAppResolver;
+Landroid/app/IInstantAppResolver$Stub;
+Landroid/app/IInstantAppResolver$Stub$Proxy;
+Landroid/app/IInstrumentationWatcher$Stub$Proxy;
+Landroid/app/Instrumentation$ActivityGoing;
+Landroid/app/Instrumentation$ActivityMonitor;
+Landroid/app/Instrumentation$ActivityWaiter;
+Landroid/app/IProcessObserver$Stub$Proxy;
+Landroid/app/IRequestFinishCallback$Stub;
+Landroid/app/IRequestFinishCallback$Stub$Proxy;
+Landroid/app/IServiceConnection$Stub$Proxy;
+Landroid/app/IStopUserCallback$Stub;
+Landroid/app/IStopUserCallback$Stub$Proxy;
+Landroid/app/ITransientNotification$Stub$Proxy;
+Landroid/app/IUiAutomationConnection$Stub$Proxy;
+Landroid/app/IUriGrantsManager$Stub$Proxy;
+Landroid/app/job/IJobService$Stub$Proxy;
+[Landroid/app/job/JobInfo$TriggerContentUri;
+Landroid/app/LoadedApk$SplitDependencyLoaderImpl;
+[Landroid/app/LoaderManagerImpl;
+[Landroid/app/Notification$Action;
+Landroid/app/Notification$BubbleMetadata;
+[Landroid/app/NotificationChannel;
+[Landroid/app/NotificationChannelGroup;
+Landroid/app/PackageInstallObserver$1;
+Landroid/app/PendingIntent$FinishedDispatcher;
+[Landroid/app/Person;
+Landroid/app/PictureInPictureParams;
+Landroid/app/prediction/AppPredictionContext;
+Landroid/app/prediction/AppPredictionSessionId;
+Landroid/app/prediction/AppPredictor;
+Landroid/app/prediction/AppTarget;
+Landroid/app/prediction/AppTargetEvent;
+Landroid/app/prediction/AppTargetId;
+Landroid/app/prediction/IPredictionCallback$Stub;
+Landroid/app/prediction/IPredictionCallback$Stub$Proxy;
+Landroid/app/prediction/IPredictionManager$Stub$Proxy;
+Landroid/app/RemoteAction;
+[Landroid/app/RemoteInput;
+Landroid/app/RemoteServiceException;
+Landroid/app/role/-$$Lambda$o94o2jK_ei-IVw-3oY_QJ49zpAA;
+Landroid/app/role/-$$Lambda$RoleControllerManager$9hUe0y0G47wIHKhViIp3z2IRAIk;
+Landroid/app/role/-$$Lambda$RoleControllerManager$Jsb4ev7pHUqel8_lglNSRLiUzpg;
+Landroid/app/role/IOnRoleHoldersChangedListener$Stub$Proxy;
+Landroid/app/role/IRoleController$Stub;
+Landroid/app/SearchableInfo;
+Landroid/app/SearchableInfo$ActionKeyInfo;
+Landroid/app/servertransaction/ActivityConfigurationChangeItem;
+[Landroid/app/slice/SliceItem;
+[Landroid/app/slice/SliceSpec;
+Landroid/app/StatsManager$StatsdDeathRecipient;
+Landroid/app/trust/TrustManager$1;
+Landroid/app/trust/TrustManager$TrustListener;
+Landroid/app/usage/ICacheQuotaService$Stub$Proxy;
+Landroid/app/usage/NetworkStatsManager$CallbackHandler;
+Landroid/app/usage/NetworkStatsManager$UsageCallback;
+Landroid/app/VoiceInteractor;
+Landroid/app/Vr2dDisplayProperties;
+Landroid/app/WallpaperInfo;
+Landroid/app/WallpaperManager$OnColorsChangedListener;
+Landroid/appwidget/AppWidgetProviderInfo;
+Landroid/bluetooth/-$$Lambda$BluetoothAdapter$2$INSd_aND-SGWhhPZUtIqya_Uxw4;
+Landroid/bluetooth/BluetoothA2dpSink;
+Landroid/bluetooth/BluetoothAvrcpController;
+[Landroid/bluetooth/BluetoothCodecConfig;
+Landroid/bluetooth/BluetoothCodecStatus;
+[Landroid/bluetooth/BluetoothDevice;
+Landroid/bluetooth/BluetoothGattService;
+Landroid/bluetooth/BluetoothHeadsetClient;
+Landroid/bluetooth/BluetoothHidDevice$1;
+Landroid/bluetooth/BluetoothHidDeviceAppQosSettings;
+Landroid/bluetooth/BluetoothHidDeviceAppSdpSettings;
+Landroid/bluetooth/BluetoothHidHost$1;
+Landroid/bluetooth/BluetoothMap$1;
+Landroid/bluetooth/BluetoothMapClient;
+Landroid/bluetooth/BluetoothPan$1;
+Landroid/bluetooth/BluetoothPbap$1;
+Landroid/bluetooth/BluetoothPbap$2;
+Landroid/bluetooth/BluetoothPbapClient;
+Landroid/bluetooth/BluetoothProfileConnector$1;
+Landroid/bluetooth/BluetoothProfileConnector$2;
+Landroid/bluetooth/BluetoothSap$1;
+Landroid/bluetooth/IBluetoothGattCallback$Stub;
+Landroid/bluetooth/IBluetoothGattCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothGattServerCallback$Stub;
+Landroid/bluetooth/IBluetoothGattServerCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidDevice$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidDeviceCallback$Stub;
+Landroid/bluetooth/IBluetoothHidDeviceCallback$Stub$Proxy;
+Landroid/bluetooth/IBluetoothHidHost$Stub$Proxy;
+Landroid/bluetooth/IBluetoothMap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothMetadataListener$Stub$Proxy;
+Landroid/bluetooth/IBluetoothPan$Stub$Proxy;
+Landroid/bluetooth/IBluetoothPbap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothProfileServiceConnection$Stub$Proxy;
+Landroid/bluetooth/IBluetoothSap$Stub$Proxy;
+Landroid/bluetooth/IBluetoothStateChangeCallback$Stub$Proxy;
+Landroid/bluetooth/le/AdvertiseData;
+Landroid/bluetooth/le/AdvertisingSetParameters;
+Landroid/bluetooth/le/BluetoothLeScanner;
+Landroid/bluetooth/le/IAdvertisingSetCallback$Stub;
+Landroid/bluetooth/le/IAdvertisingSetCallback$Stub$Proxy;
+Landroid/bluetooth/le/IPeriodicAdvertisingCallback$Stub;
+Landroid/bluetooth/le/IPeriodicAdvertisingCallback$Stub$Proxy;
+Landroid/bluetooth/le/IScannerCallback$Stub;
+Landroid/bluetooth/le/PeriodicAdvertisingParameters;
+Landroid/bluetooth/OobData;
+[Landroid/bluetooth/UidTraffic;
+Landroid/companion/AssociationRequest;
+Landroid/companion/ICompanionDeviceManager$Stub$Proxy;
+Landroid/companion/IFindDeviceCallback$Stub;
+Landroid/companion/IFindDeviceCallback$Stub$Proxy;
+Landroid/compat/Compatibility;
+Landroid/content/ClipData;
+Landroid/content/ClipData$Item;
+Landroid/content/ClipDescription;
+Landroid/content/ContentProviderClient$NotRespondingRunnable;
+[Landroid/content/ContentValues;
+[Landroid/content/Intent;
+[Landroid/content/IntentFilter;
+Landroid/content/IntentSender$FinishedDispatcher;
+Landroid/content/IOnPrimaryClipChangedListener$Stub$Proxy;
+Landroid/content/IRestrictionsManager$Stub$Proxy;
+Landroid/content/ISyncAdapter$Stub$Proxy;
+Landroid/content/ISyncStatusObserver$Stub$Proxy;
+Landroid/content/LocusId;
+Landroid/content/om/IOverlayManager$Stub$Proxy;
+Landroid/content/pm/-$$Lambda$ciir_QAmv6RwJro4I58t77dPnxU;
+Landroid/content/pm/-$$Lambda$n3uXeb1v-YRmq_BWTfosEqUUr9g;
+Landroid/content/pm/-$$Lambda$PackageParser$0aobsT7Zf7WVZCqMZ5z2clAuQf4;
+Landroid/content/pm/-$$Lambda$PackageParser$0DZRgzfgaIMpCOhJqjw6PUiU5vw;
+Landroid/content/pm/-$$Lambda$PackageParser$M-9fHqS_eEp1oYkuKJhRHOGUxf8;
+Landroid/content/pm/-$$Lambda$T1UQAuePWRRmVQ1KzTyMAktZUPM;
+Landroid/content/pm/-$$Lambda$zO9HBUVgPeroyDQPLJE-MNMvSqc;
+[Landroid/content/pm/ActivityInfo;
+Landroid/content/pm/BaseParceledListSlice$1;
+[Landroid/content/pm/ConfigurationInfo;
+Landroid/content/pm/dex/DexMetadataHelper;
+Landroid/content/pm/dex/ISnapshotRuntimeProfileCallback$Stub;
+Landroid/content/pm/dex/ISnapshotRuntimeProfileCallback$Stub$Proxy;
+[Landroid/content/pm/FeatureGroupInfo;
+[Landroid/content/pm/FeatureInfo;
+Landroid/content/pm/IDexModuleRegisterCallback$Stub;
+Landroid/content/pm/IDexModuleRegisterCallback$Stub$Proxy;
+Landroid/content/pm/InstantAppIntentFilter;
+Landroid/content/pm/InstantAppResolveInfo;
+Landroid/content/pm/InstantAppResolveInfo$InstantAppDigest;
+[Landroid/content/pm/InstrumentationInfo;
+Landroid/content/pm/IOnAppsChangedListener$Stub$Proxy;
+Landroid/content/pm/IPackageDataObserver$Stub$Proxy;
+Landroid/content/pm/IPackageDeleteObserver$Stub;
+Landroid/content/pm/IPackageDeleteObserver$Stub$Proxy;
+Landroid/content/pm/IPackageDeleteObserver2$Stub;
+Landroid/content/pm/IPackageDeleteObserver2$Stub$Proxy;
+Landroid/content/pm/IPackageInstallerCallback$Stub$Proxy;
+Landroid/content/pm/IPackageMoveObserver$Stub$Proxy;
+Landroid/content/pm/IPackageStatsObserver$Stub$Proxy;
+Landroid/content/pm/KeySet;
+Landroid/content/pm/LauncherActivityInfo;
+Landroid/content/pm/LauncherApps$CallbackMessageHandler;
+[Landroid/content/pm/PackageInfo;
+Landroid/content/pm/PackageParser$Instrumentation;
+[Landroid/content/pm/PathPermission;
+[Landroid/content/pm/PermissionInfo;
+[Landroid/content/pm/ProviderInfo;
+[Landroid/content/pm/ServiceInfo;
+[Landroid/content/pm/Signature;
+Landroid/content/pm/SigningInfo;
+Landroid/content/pm/split/SplitAssetDependencyLoader;
+Landroid/content/pm/split/SplitDependencyLoader;
+Landroid/content/pm/SuspendDialogInfo;
+[Landroid/content/pm/VerifierInfo;
+Landroid/content/res/AssetFileDescriptor$AutoCloseInputStream;
+[Landroid/content/res/ColorStateList;
+Landroid/content/res/CompatibilityInfo$Translator;
+Landroid/content/res/FontResourcesParser$FontFamilyFilesResourceEntry;
+[Landroid/content/res/FontResourcesParser$FontFileResourceEntry;
+Landroid/content/res/GradientColor$GradientColorFactory;
+Landroid/content/res/StringBlock$Height;
+[Landroid/content/res/XmlBlock;
+Landroid/content/rollback/IRollbackManager$Stub$Proxy;
+[Landroid/content/SyncAdapterType;
+[Landroid/content/UndoOwner;
+[Landroid/database/CursorWindow;
+Landroid/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException;
+[Landroid/database/sqlite/SQLiteConnection$Operation;
+Landroid/database/sqlite/SQLiteConnectionPool$1;
+Landroid/database/sqlite/SQLiteDebug$NoPreloadHolder;
+Landroid/database/sqlite/SqliteWrapper;
+Landroid/database/StaleDataException;
+Landroid/graphics/-$$Lambda$ColorSpace$Rgb$iMkODTKa3_8kPZUnZZerD2Lv-yo;
+Landroid/graphics/-$$Lambda$ColorSpace$Rgb$V_0lmM2WEpxGBDV_1G1wvvidn7Y;
+Landroid/graphics/BlendMode$1;
+[Landroid/graphics/ColorSpace;
+Landroid/graphics/ColorSpace$RenderIntent;
+Landroid/graphics/drawable/-$$Lambda$AnimatedVectorDrawable$VectorDrawableAnimatorRT$PzjgSeyQweoFjbEZJP80UteZqm8;
+Landroid/graphics/drawable/-$$Lambda$Drawable$bbJz2VgQAwkXlE27mR8nPMYacEw;
+[Landroid/graphics/drawable/AdaptiveIconDrawable$ChildDrawable;
+Landroid/graphics/drawable/AnimatedImageDrawable$State;
+Landroid/graphics/drawable/AnimatedRotateDrawable$1;
+Landroid/graphics/drawable/AnimatedStateListDrawable$AnimatableTransition;
+Landroid/graphics/drawable/AnimatedStateListDrawable$AnimationDrawableTransition;
+Landroid/graphics/drawable/AnimatedVectorDrawable$2;
+Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimatorUI;
+Landroid/graphics/drawable/ColorStateListDrawable;
+Landroid/graphics/drawable/ColorStateListDrawable$ColorStateListDrawableState;
+[Landroid/graphics/drawable/Drawable;
+Landroid/graphics/drawable/DrawableContainer$1;
+[Landroid/graphics/drawable/LayerDrawable$ChildDrawable;
+Landroid/graphics/drawable/LevelListDrawable;
+Landroid/graphics/drawable/LevelListDrawable$LevelListState;
+[Landroid/graphics/drawable/RippleForeground;
+[Landroid/graphics/fonts/FontFamily;
+Landroid/graphics/fonts/FontFileUtil;
+[Landroid/graphics/fonts/FontVariationAxis;
+Landroid/graphics/LeakyTypefaceStorage;
+Landroid/graphics/Rect$UnflattenHelper;
+Landroid/gsi/IGsiService;
+Landroid/gsi/IGsiService$Stub;
+Landroid/gsi/IGsiService$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback$Stub;
+Landroid/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricService$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricServiceReceiver$Stub;
+Landroid/hardware/biometrics/IBiometricServiceReceiver$Stub$Proxy;
+Landroid/hardware/biometrics/IBiometricServiceReceiverInternal$Stub$Proxy;
+Landroid/hardware/camera2/CameraManager$AvailabilityCallback;
+Landroid/hardware/camera2/CameraManager$CameraManagerGlobal;
+Landroid/hardware/camera2/CameraManager$TorchCallback;
+Landroid/hardware/camera2/impl/CameraDeviceImpl;
+Landroid/hardware/camera2/impl/CameraDeviceImpl$CameraHandlerExecutor;
+Landroid/hardware/camera2/legacy/LegacyMetadataMapper;
+Landroid/hardware/camera2/marshal/impl/MarshalQueryableBoolean$MarshalerBoolean;
+[Landroid/hardware/camera2/marshal/MarshalQueryable;
+Landroid/hardware/camera2/utils/ArrayUtils;
+[Landroid/hardware/CameraStatus;
+Landroid/hardware/CameraStatus;
+Landroid/hardware/contexthub/V1_0/ContextHubMsg;
+Landroid/hardware/contexthub/V1_0/MemRange;
+Landroid/hardware/contexthub/V1_0/PhysicalSensor;
+Landroid/hardware/display/-$$Lambda$NightDisplayListener$sOK1HmSbMnFLzc4SdDD1WpVWJiI;
+Landroid/hardware/display/IVirtualDisplayCallback$Stub;
+Landroid/hardware/display/IVirtualDisplayCallback$Stub$Proxy;
+Landroid/hardware/display/NightDisplayListener;
+Landroid/hardware/display/NightDisplayListener$1;
+Landroid/hardware/display/Time;
+Landroid/hardware/fingerprint/IFingerprintClientActiveCallback$Stub;
+Landroid/hardware/fingerprint/IFingerprintClientActiveCallback$Stub$Proxy;
+Landroid/hardware/fingerprint/IFingerprintServiceReceiver$Stub$Proxy;
+Landroid/hardware/input/IInputDevicesChangedListener$Stub$Proxy;
+Landroid/hardware/input/ITabletModeChangedListener$Stub;
+Landroid/hardware/input/ITabletModeChangedListener$Stub$Proxy;
+Landroid/hardware/ISensorPrivacyManager$Stub$Proxy;
+Landroid/hardware/location/ContextHubClient;
+Landroid/hardware/location/ContextHubManager$2;
+Landroid/hardware/location/ContextHubManager$3;
+Landroid/hardware/location/ContextHubManager$4;
+Landroid/hardware/location/ContextHubMessage;
+Landroid/hardware/location/ContextHubTransaction;
+Landroid/hardware/location/GeofenceHardwareImpl;
+Landroid/hardware/location/GeofenceHardwareImpl$1;
+Landroid/hardware/location/GeofenceHardwareImpl$2;
+Landroid/hardware/location/GeofenceHardwareImpl$3;
+Landroid/hardware/location/GeofenceHardwareImpl$GeofenceTransition;
+Landroid/hardware/location/GeofenceHardwareImpl$Reaper;
+Landroid/hardware/location/GeofenceHardwareMonitorEvent;
+Landroid/hardware/location/GeofenceHardwareRequest;
+[Landroid/hardware/location/GeofenceHardwareRequestParcelable;
+Landroid/hardware/location/GeofenceHardwareRequestParcelable;
+Landroid/hardware/location/GeofenceHardwareService$1;
+Landroid/hardware/location/IActivityRecognitionHardware$Stub$Proxy;
+Landroid/hardware/location/IActivityRecognitionHardwareClient;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;
+Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub$Proxy;
+Landroid/hardware/location/IContextHubCallback$Stub$Proxy;
+Landroid/hardware/location/IContextHubClient$Stub$Proxy;
+Landroid/hardware/location/IContextHubClientCallback$Stub$Proxy;
+Landroid/hardware/location/IContextHubService$Stub$Proxy;
+Landroid/hardware/location/IContextHubTransactionCallback$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardware;
+Landroid/hardware/location/IGeofenceHardware$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardwareCallback;
+Landroid/hardware/location/IGeofenceHardwareCallback$Stub;
+Landroid/hardware/location/IGeofenceHardwareCallback$Stub$Proxy;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback$Stub;
+Landroid/hardware/location/IGeofenceHardwareMonitorCallback$Stub$Proxy;
+[Landroid/hardware/location/MemoryRegion;
+Landroid/hardware/location/NanoApp;
+Landroid/hardware/location/NanoAppBinary;
+Landroid/hardware/location/NanoAppFilter;
+Landroid/hardware/radio/config/V1_1/ModemsConfig;
+Landroid/hardware/radio/config/V1_2/SimSlotStatus;
+Landroid/hardware/radio/deprecated/V1_0/IOemHook$Proxy;
+Landroid/hardware/radio/V1_0/Call;
+Landroid/hardware/radio/V1_0/CallForwardInfo;
+Landroid/hardware/radio/V1_0/CarrierRestrictions;
+Landroid/hardware/radio/V1_0/CdmaCallWaiting;
+Landroid/hardware/radio/V1_0/CdmaInformationRecords;
+Landroid/hardware/radio/V1_0/CdmaSignalInfoRecord;
+Landroid/hardware/radio/V1_0/CdmaSmsAddress;
+Landroid/hardware/radio/V1_0/CdmaSmsMessage;
+Landroid/hardware/radio/V1_0/CdmaSmsSubaddress;
+Landroid/hardware/radio/V1_0/CellIdentity;
+Landroid/hardware/radio/V1_0/CellIdentityCdma;
+Landroid/hardware/radio/V1_0/CellIdentityGsm;
+Landroid/hardware/radio/V1_0/CellIdentityTdscdma;
+Landroid/hardware/radio/V1_0/CellIdentityWcdma;
+Landroid/hardware/radio/V1_0/CellInfo;
+Landroid/hardware/radio/V1_0/CellInfoGsm;
+Landroid/hardware/radio/V1_0/CellInfoLte;
+Landroid/hardware/radio/V1_0/CellInfoTdscdma;
+Landroid/hardware/radio/V1_0/CellInfoWcdma;
+Landroid/hardware/radio/V1_0/DataCallFailCause;
+Landroid/hardware/radio/V1_0/GsmSmsMessage;
+Landroid/hardware/radio/V1_0/HardwareConfigModem;
+Landroid/hardware/radio/V1_0/HardwareConfigSim;
+Landroid/hardware/radio/V1_0/ImsSmsMessage;
+Landroid/hardware/radio/V1_0/LastCallFailCauseInfo;
+Landroid/hardware/radio/V1_0/LceDataInfo;
+Landroid/hardware/radio/V1_0/LceStatusInfo;
+Landroid/hardware/radio/V1_0/NeighboringCell;
+Landroid/hardware/radio/V1_0/OperatorInfo;
+Landroid/hardware/radio/V1_0/PcoDataInfo;
+Landroid/hardware/radio/V1_0/SendSmsResult;
+Landroid/hardware/radio/V1_0/SignalStrength;
+Landroid/hardware/radio/V1_0/SimApdu;
+Landroid/hardware/radio/V1_0/SimRefreshResult;
+Landroid/hardware/radio/V1_0/StkCcUnsolSsResult;
+Landroid/hardware/radio/V1_0/SuppSvcNotification;
+Landroid/hardware/radio/V1_0/UusInfo;
+Landroid/hardware/radio/V1_1/IRadio$Proxy;
+Landroid/hardware/radio/V1_1/KeepaliveStatus;
+Landroid/hardware/radio/V1_1/NetworkScanResult;
+Landroid/hardware/radio/V1_2/CellIdentityCdma;
+Landroid/hardware/radio/V1_2/CellIdentityGsm;
+Landroid/hardware/radio/V1_2/CellIdentityLte;
+Landroid/hardware/radio/V1_2/CellIdentityTdscdma;
+Landroid/hardware/radio/V1_2/CellIdentityWcdma;
+Landroid/hardware/radio/V1_2/CellInfoCdma;
+Landroid/hardware/radio/V1_2/CellInfoGsm;
+Landroid/hardware/radio/V1_2/CellInfoLte;
+Landroid/hardware/radio/V1_2/CellInfoTdscdma;
+Landroid/hardware/radio/V1_2/CellInfoWcdma;
+Landroid/hardware/radio/V1_2/LinkCapacityEstimate;
+Landroid/hardware/radio/V1_2/NetworkScanResult;
+Landroid/hardware/radio/V1_2/PhysicalChannelConfig;
+Landroid/hardware/radio/V1_2/TdscdmaSignalStrength;
+Landroid/hardware/radio/V1_4/CardStatus;
+Landroid/hardware/radio/V1_4/CarrierRestrictionsWithPriority;
+Landroid/hardware/radio/V1_4/CellInfo;
+Landroid/hardware/radio/V1_4/DataRegStateResult;
+Landroid/hardware/radio/V1_4/DataRegStateResult$VopsInfo;
+Landroid/hardware/radio/V1_4/EmergencyNumber;
+Landroid/hardware/radio/V1_4/NetworkScanResult;
+Landroid/hardware/radio/V1_4/NrIndicators;
+Landroid/hardware/radio/V1_4/PhysicalChannelConfig;
+Landroid/hardware/radio/V1_4/SetupDataCallResult;
+Landroid/hardware/radio/V1_4/SignalStrength;
+Landroid/hardware/SensorAdditionalInfo;
+Landroid/hardware/SensorPrivacyManager$1;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback$Stub;
+Landroid/hardware/soundtrigger/IRecognitionStatusCallback$Stub$Proxy;
+[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;
+[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;
+Landroid/hardware/soundtrigger/SoundTriggerModule$NativeEventHandlerDelegate;
+Landroid/hardware/soundtrigger/SoundTriggerModule$NativeEventHandlerDelegate$1;
+Landroid/hardware/SystemSensorManager$TriggerEventQueue;
+Landroid/hardware/TriggerEvent;
+Landroid/hardware/usb/UsbAccessory;
+Landroid/icu/impl/locale/InternalLocaleBuilder;
+Landroid/icu/impl/locale/LanguageTag;
+Landroid/icu/text/BidiLine;
+Landroid/icu/text/Edits$Iterator;
+Landroid/inputmethodservice/-$$Lambda$InputMethodService$8T9TmAUIN7vW9eU6kTg8309_d4E;
+Landroid/inputmethodservice/-$$Lambda$InputMethodService$wp8DeVGx_WDOPw4F6an7QbwVxf0;
+Landroid/inputmethodservice/AbstractInputMethodService;
+Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodImpl;
+Landroid/inputmethodservice/AbstractInputMethodService$AbstractInputMethodSessionImpl;
+Landroid/inputmethodservice/IInputMethodSessionWrapper;
+Landroid/inputmethodservice/IInputMethodSessionWrapper$ImeInputEventReceiver;
+Landroid/inputmethodservice/IInputMethodWrapper;
+Landroid/inputmethodservice/IInputMethodWrapper$InputMethodSessionCallbackWrapper;
+Landroid/inputmethodservice/InputMethodService;
+Landroid/inputmethodservice/InputMethodService$InputMethodSessionImpl;
+Landroid/inputmethodservice/InputMethodService$Insets;
+Landroid/inputmethodservice/InputMethodService$SettingsObserver;
+Landroid/internal/hidl/base/V1_0/DebugInfo;
+Landroid/location/Country;
+Landroid/location/CountryDetector$ListenerTransport;
+Landroid/location/Criteria;
+Landroid/location/Geofence;
+Landroid/location/GnssClock;
+Landroid/location/GnssMeasurement;
+Landroid/location/GnssMeasurementCorrections;
+Landroid/location/IBatchedLocationCallback$Stub$Proxy;
+Landroid/location/ICountryListener$Stub;
+Landroid/location/ICountryListener$Stub$Proxy;
+Landroid/location/IFusedGeofenceHardware$Stub;
+Landroid/location/IFusedGeofenceHardware$Stub$Proxy;
+Landroid/location/IGeocodeProvider;
+Landroid/location/IGeocodeProvider$Stub;
+Landroid/location/IGeocodeProvider$Stub$Proxy;
+Landroid/location/IGeofenceProvider;
+Landroid/location/IGeofenceProvider$Stub;
+Landroid/location/IGeofenceProvider$Stub$Proxy;
+Landroid/location/IGnssMeasurementsListener$Stub$Proxy;
+Landroid/location/IGnssNavigationMessageListener$Stub$Proxy;
+Landroid/location/IGnssStatusListener$Stub$Proxy;
+Landroid/location/IGpsGeofenceHardware$Stub$Proxy;
+Landroid/location/ILocationListener$Stub$Proxy;
+Landroid/location/LocationManager$GnssStatusListenerTransport$1;
+Landroid/media/-$$Lambda$MediaCodecInfo$VideoCapabilities$DpgwEn-gVFZT9EtP3qcxpiA2G0M;
+[Landroid/media/AudioDeviceInfo;
+Landroid/media/AudioFocusInfo;
+Landroid/media/AudioFocusRequest;
+Landroid/media/AudioManager$BlockingFocusResultReceiver;
+Landroid/media/AudioManager$SafeWaitObject;
+[Landroid/media/AudioPatch;
+Landroid/media/audiopolicy/AudioPolicyConfig;
+Landroid/media/audiopolicy/IAudioPolicyCallback;
+Landroid/media/audiopolicy/IAudioPolicyCallback$Stub;
+Landroid/media/audiopolicy/IAudioPolicyCallback$Stub$Proxy;
+[Landroid/media/AudioPort;
+Landroid/media/AudioRecordingMonitorImpl;
+Landroid/media/AudioRecordingMonitorImpl$1;
+[Landroid/media/ExifInterface$Rational;
+Landroid/media/ExifInterface$Rational;
+Landroid/media/IAudioFocusDispatcher$Stub$Proxy;
+Landroid/media/IAudioServerStateDispatcher$Stub$Proxy;
+Landroid/media/IMediaRouter2Client$Stub;
+Landroid/media/IMediaRouter2Client$Stub$Proxy;
+Landroid/media/IMediaRouter2Manager$Stub;
+Landroid/media/IMediaRouter2Manager$Stub$Proxy;
+Landroid/media/IPlaybackConfigDispatcher$Stub$Proxy;
+Landroid/media/IRecordingConfigDispatcher$Stub$Proxy;
+Landroid/media/IRemoteVolumeController$Stub$Proxy;
+Landroid/media/IVolumeController$Stub$Proxy;
+Landroid/media/MediaCodec$BufferMap$CodecBuffer;
+[Landroid/media/MediaCodecInfo;
+[Landroid/media/MediaCodecInfo$CodecCapabilities;
+[Landroid/media/MediaCodecInfo$CodecProfileLevel;
+Landroid/media/MediaCodecInfo$VideoCapabilities$PerformancePoint;
+Landroid/media/MediaHTTPService;
+Landroid/media/MediaPlayer$6;
+Landroid/media/MediaPlayer$DrmInfo;
+Landroid/media/MediaPlayer$EventHandler$1;
+Landroid/media/MediaPlayer$EventHandler$2;
+[Landroid/media/MediaPlayer$TrackInfo;
+Landroid/media/MediaPlayer$TrackInfo;
+Landroid/media/MediaRoute2Info;
+Landroid/media/MediaRouter$RouteGroup;
+Landroid/media/MediaRouterClientState;
+Landroid/media/MediaRouterClientState$RouteInfo;
+[Landroid/media/MediaTimeProvider$OnMediaTimeListener;
+Landroid/media/NativeRoutingEventHandlerDelegate;
+Landroid/media/projection/IMediaProjection$Stub;
+Landroid/media/projection/IMediaProjection$Stub$Proxy;
+Landroid/media/projection/IMediaProjectionManager$Stub$Proxy;
+Landroid/media/projection/IMediaProjectionWatcherCallback$Stub$Proxy;
+Landroid/media/projection/MediaProjectionManager$CallbackDelegate;
+Landroid/media/Rating;
+Landroid/media/Ringtone;
+Landroid/media/Session2Token;
+Landroid/media/session/IActiveSessionsListener$Stub$Proxy;
+Landroid/media/session/ICallback$Stub$Proxy;
+Landroid/media/session/IOnMediaKeyListener$Stub$Proxy;
+Landroid/media/session/IOnVolumeKeyLongPressListener$Stub$Proxy;
+Landroid/media/session/ISessionCallback$Stub$Proxy;
+Landroid/media/session/ISessionControllerCallback$Stub$Proxy;
+Landroid/media/session/MediaController$PlaybackInfo;
+Landroid/media/session/MediaSessionManager$CallbackStub;
+Landroid/media/SubtitleController;
+Landroid/media/SubtitleController$2;
+Landroid/media/SubtitleData;
+Landroid/media/SubtitleTrack;
+Landroid/media/TimedMetaData;
+Landroid/media/TimedText;
+Landroid/net/-$$Lambda$Network$KD6DxaMRJIcajhj36TU1K7lJnHQ;
+Landroid/net/-$$Lambda$NetworkFactory$HfslgqyaKc_n0wXX5_qRYVZoGfI;
+Landroid/net/-$$Lambda$NetworkStats$xvFSsVoR0k5s7Fhw1yPDPVIpx8A;
+Landroid/net/ConnectionInfo;
+Landroid/net/DataUsageRequest;
+Landroid/net/EventLogTags;
+Landroid/net/IIpConnectivityMetrics$Stub$Proxy;
+Landroid/net/INetdEventCallback$Stub$Proxy;
+Landroid/net/INetworkManagementEventObserver$Stub$Proxy;
+Landroid/net/INetworkPolicyListener$Stub$Proxy;
+Landroid/net/INetworkRecommendationProvider;
+Landroid/net/INetworkRecommendationProvider$Stub;
+Landroid/net/INetworkRecommendationProvider$Stub$Proxy;
+Landroid/net/INetworkScoreCache$Stub$Proxy;
+Landroid/net/ISocketKeepaliveCallback$Stub;
+Landroid/net/ISocketKeepaliveCallback$Stub$Proxy;
+Landroid/net/ITetheringEventCallback$Stub;
+Landroid/net/ITetheringEventCallback$Stub$Proxy;
+Landroid/net/ITetheringStatsProvider$Stub$Proxy;
+Landroid/net/LinkProperties$CompareResult;
+Landroid/net/metrics/ApfStats;
+Landroid/net/metrics/ConnectStats;
+Landroid/net/metrics/DhcpClientEvent;
+Landroid/net/metrics/DnsEvent;
+Landroid/net/metrics/IpManagerEvent;
+Landroid/net/metrics/IpReachabilityEvent;
+Landroid/net/metrics/NetworkEvent;
+Landroid/net/metrics/NetworkMetrics$Metrics;
+Landroid/net/metrics/NetworkMetrics$Summary;
+Landroid/net/metrics/RaEvent;
+Landroid/net/metrics/ValidationProbeEvent;
+Landroid/net/metrics/WakeupStats;
+[Landroid/net/Network;
+[Landroid/net/NetworkCapabilities;
+[Landroid/net/NetworkKey;
+[Landroid/net/NetworkPolicy;
+Landroid/net/NetworkPolicyManager$1;
+[Landroid/net/NetworkState;
+Landroid/net/NetworkStatsHistory$Entry;
+Landroid/net/NetworkStatsHistory$ParcelUtils;
+Landroid/net/nsd/INsdManager$Stub$Proxy;
+Landroid/net/nsd/NsdManager$ServiceHandler;
+Landroid/net/PacProxySelector;
+Landroid/net/RssiCurve;
+[Landroid/net/ScoredNetwork;
+Landroid/net/shared/Inet4AddressUtils;
+Landroid/net/shared/InetAddressUtils;
+Landroid/net/sip/ISipService$Stub$Proxy;
+Landroid/net/SntpClient;
+Landroid/net/SntpClient$InvalidServerReplyException;
+Landroid/net/StaticIpConfiguration$Builder;
+[Landroid/net/UidRange;
+[Landroid/net/Uri;
+[Landroid/net/wifi/AnqpInformationElement;
+Landroid/net/wifi/AnqpInformationElement;
+Landroid/net/wifi/hotspot2/IProvisioningCallback$Stub;
+Landroid/net/wifi/hotspot2/IProvisioningCallback$Stub$Proxy;
+Landroid/net/wifi/IDppCallback$Stub;
+Landroid/net/wifi/IDppCallback$Stub$Proxy;
+Landroid/net/wifi/INetworkRequestMatchCallback$Stub;
+Landroid/net/wifi/INetworkRequestMatchCallback$Stub$Proxy;
+Landroid/net/wifi/IOnWifiUsabilityStatsListener$Stub;
+Landroid/net/wifi/IOnWifiUsabilityStatsListener$Stub$Proxy;
+Landroid/net/wifi/ISoftApCallback$Stub$Proxy;
+Landroid/net/wifi/ITrafficStateCallback$Stub$Proxy;
+Landroid/net/wifi/IWifiScanner$Stub$Proxy;
+Landroid/net/wifi/p2p/IWifiP2pManager$Stub$Proxy;
+Landroid/net/wifi/rtt/IRttCallback$Stub;
+Landroid/net/wifi/rtt/IRttCallback$Stub$Proxy;
+Landroid/net/wifi/rtt/RangingRequest;
+[Landroid/net/wifi/ScanResult$InformationElement;
+[Landroid/net/wifi/ScanResult$RadioChainInfo;
+Landroid/net/wifi/SupplicantState$2;
+Landroid/net/wifi/WifiConfiguration$KeyMgmt;
+Landroid/net/wifi/WifiManager$MulticastLock;
+Landroid/net/wifi/WifiManager$SoftApCallbackProxy;
+Landroid/net/wifi/WifiManager$TrafficStateCallbackProxy;
+Landroid/net/wifi/WifiNetworkScoreCache$CacheListener$1;
+Landroid/net/wifi/WifiNetworkSpecifier;
+Landroid/net/wifi/WifiNetworkSuggestion;
+[Landroid/net/wifi/WifiScanner$ChannelSpec;
+Landroid/net/wifi/WifiScanner$OperationResult;
+Landroid/net/wifi/WifiScanner$ParcelableScanData;
+Landroid/net/wifi/WifiScanner$ParcelableScanResults;
+[Landroid/net/wifi/WifiScanner$ScanSettings$HiddenNetwork;
+Landroid/net/wifi/WifiScanner$ScanSettings$HiddenNetwork;
+Landroid/nfc/BeamShareData;
+Landroid/nfc/IAppCallback$Stub$Proxy;
+Landroid/nfc/INfcUnlockHandler$Stub;
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;
+Landroid/nfc/ITagRemovedCallback$Stub;
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;
+Landroid/nfc/Tag;
+Landroid/nfc/TechListParcel;
+Landroid/os/-$$Lambda$HidlSupport$GHxmwrIWiKN83tl6aMQt_nV5hiw;
+Landroid/os/-$$Lambda$PowerManager$WakeLock$VvFzmRZ4ZGlXx7u3lSAJ_T-YUjw;
+Landroid/os/-$$Lambda$StrictMode$AndroidBlockGuardPolicy$FxZGA9KtfTewqdcxlUwvIe5Nx9I;
+Landroid/os/-$$Lambda$StrictMode$UFC_nI1x6u8ZwMQmA7bmj9NHZz4;
+Landroid/os/AsyncTask$5;
+Landroid/os/BatteryProperty;
+Landroid/os/BatterySaverPolicyConfig;
+Landroid/os/BatteryStats$2;
+Landroid/os/BatteryStats$HistoryPrinter;
+[Landroid/os/Bundle;
+Landroid/os/connectivity/CellularBatteryStats;
+Landroid/os/connectivity/GpsBatteryStats;
+Landroid/os/connectivity/WifiBatteryStats;
+[Landroid/os/Debug$MemoryInfo;
+Landroid/os/FileUtils$1;
+Landroid/os/GraphicsEnvironment$1;
+Landroid/os/health/HealthKeys$Constant;
+[Landroid/os/health/HealthKeys$SortedIntArray;
+Landroid/os/health/HealthKeys$SortedIntArray;
+Landroid/os/health/HealthStatsWriter;
+Landroid/os/IDeviceIdentifiersPolicyService$Stub$Proxy;
+Landroid/os/IMaintenanceActivityListener$Stub;
+Landroid/os/IMaintenanceActivityListener$Stub$Proxy;
+Landroid/os/INetworkActivityListener;
+Landroid/os/INetworkActivityListener$Stub;
+Landroid/os/INetworkActivityListener$Stub$Proxy;
+Landroid/os/IProgressListener$Stub$Proxy;
+Landroid/os/IRemoteCallback$Stub$Proxy;
+Landroid/os/IServiceManager$Stub;
+Landroid/os/IServiceManager$Stub$Proxy;
+Landroid/os/IThermalEventListener$Stub$Proxy;
+Landroid/os/IThermalService$Stub$Proxy;
+Landroid/os/IThermalStatusListener$Stub$Proxy;
+Landroid/os/IVoldTaskListener$Stub;
+Landroid/os/IVoldTaskListener$Stub$Proxy;
+Landroid/os/MessageQueue$FileDescriptorRecord;
+[Landroid/os/MessageQueue$IdleHandler;
+Landroid/os/NetworkOnMainThreadException;
+[Landroid/os/Parcelable;
+[Landroid/os/ParcelFileDescriptor;
+[Landroid/os/ParcelUuid;
+[Landroid/os/PatternMatcher;
+[Landroid/os/PersistableBundle;
+[Landroid/os/RegistrantList;
+Landroid/os/RemoteCallback$1;
+Landroid/os/RemoteCallback$2;
+Landroid/os/SharedMemory$Closer;
+Landroid/os/SharedMemory$MemoryRegistration;
+Landroid/os/SharedMemory$Unmapper;
+Landroid/os/StatsDimensionsValue;
+[Landroid/os/storage/DiskInfo;
+Landroid/os/storage/IObbActionListener$Stub$Proxy;
+Landroid/os/storage/IStorageEventListener$Stub$Proxy;
+Landroid/os/storage/IStorageShutdownObserver$Stub;
+Landroid/os/storage/IStorageShutdownObserver$Stub$Proxy;
+[Landroid/os/storage/StorageVolume;
+[Landroid/os/storage/VolumeInfo;
+[Landroid/os/storage/VolumeRecord;
+Landroid/os/strictmode/ContentUriWithoutPermissionViolation;
+Landroid/os/strictmode/CredentialProtectedWhileLockedViolation;
+Landroid/os/strictmode/CustomViolation;
+Landroid/os/strictmode/DiskWriteViolation;
+Landroid/os/strictmode/ExplicitGcViolation;
+Landroid/os/strictmode/ImplicitDirectBootViolation;
+Landroid/os/strictmode/IntentReceiverLeakedViolation;
+Landroid/os/strictmode/LeakedClosableViolation;
+Landroid/os/strictmode/NetworkViolation;
+Landroid/os/strictmode/ResourceMismatchViolation;
+Landroid/os/strictmode/ServiceConnectionLeakedViolation;
+Landroid/os/strictmode/SqliteObjectLeakedViolation;
+Landroid/os/strictmode/UnbufferedIoViolation;
+Landroid/os/strictmode/UntaggedSocketViolation;
+Landroid/os/SystemService;
+Landroid/os/SystemService$State;
+[Landroid/os/WorkSource;
+Landroid/permission/-$$Lambda$PermissionControllerManager$Iy-7wiKMCV-MFSPGyIJxP_DSf8E;
+Landroid/permission/-$$Lambda$PermissionControllerManager$yYvntg8BoN_6kTWlcdYA7GFZJjs;
+Landroid/permission/IPermissionController$Stub;
+Landroid/permission/IPermissionManager$Stub;
+Landroid/permission/IPermissionManager$Stub$Proxy;
+Landroid/preference/PreferenceInflater;
+Landroid/preference/PreferenceScreen;
+Landroid/print/IPrintSpooler;
+Landroid/print/IPrintSpooler$Stub;
+Landroid/print/IPrintSpooler$Stub$Proxy;
+Landroid/print/PrintJobInfo;
+Landroid/printservice/PrintServiceInfo;
+Landroid/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder;
+Landroid/privacy/internal/rappor/RapporConfig;
+Landroid/privacy/internal/rappor/RapporEncoder;
+Landroid/provider/BlockedNumberContract;
+Landroid/provider/BlockedNumberContract$SystemContract;
+Landroid/provider/ContactsContract$PhoneLookup;
+Landroid/provider/DeviceConfig$2;
+Landroid/provider/Downloads;
+[Landroid/provider/FontsContract$FontInfo;
+Landroid/provider/Telephony$Mms;
+Landroid/security/IKeyChainService$Stub;
+Landroid/security/IKeyChainService$Stub$Proxy;
+Landroid/security/keymaster/KeymasterBooleanArgument;
+Landroid/security/keymaster/KeymasterDateArgument;
+Landroid/security/keymaster/KeymasterDefs;
+Landroid/security/keymaster/KeymasterLongArgument;
+Landroid/security/KeyStore$KeyCharacteristicsCallbackResult;
+Landroid/security/KeyStore$KeyCharacteristicsPromise;
+Landroid/security/KeyStore$KeystoreResultPromise;
+Landroid/security/KeyStore$OperationPromise;
+Landroid/security/KeyStore$State;
+Landroid/security/keystore/AndroidKeyStoreLoadStoreParameter;
+Landroid/security/keystore/AndroidKeyStorePrivateKey;
+Landroid/security/keystore/AndroidKeyStoreSecretKey;
+Landroid/security/keystore/ArrayUtils;
+Landroid/security/keystore/KeyProperties$Digest;
+Landroid/security/keystore/KeyStoreConnectException;
+Landroid/security/keystore/KeystoreResponse;
+Landroid/security/keystore/recovery/KeyChainProtectionParams;
+Landroid/security/keystore/recovery/KeyChainSnapshot;
+Landroid/security/keystore/recovery/KeyDerivationParams;
+Landroid/security/keystore/recovery/RecoveryCertPath;
+Landroid/security/keystore/recovery/WrappedApplicationKey;
+Landroid/security/keystore/recovery/X509CertificateParsingUtils;
+Landroid/security/keystore/UserNotAuthenticatedException;
+Landroid/security/keystore/Utils;
+Landroid/security/net/config/Domain;
+Landroid/security/net/config/ResourceCertificateSource;
+Landroid/security/net/config/TrustedCertificateStoreAdapter;
+Landroid/security/net/config/UserCertificateSource$NoPreloadHolder;
+Landroid/security/net/config/WfaCertificateSource;
+Landroid/security/net/config/WfaCertificateSource$NoPreloadHolder;
+Landroid/service/appprediction/IPredictionService;
+Landroid/service/appprediction/IPredictionService$Stub;
+Landroid/service/appprediction/IPredictionService$Stub$Proxy;
+Landroid/service/autofill/augmented/IAugmentedAutofillService;
+Landroid/service/autofill/augmented/IAugmentedAutofillService$Stub;
+Landroid/service/autofill/augmented/IAugmentedAutofillService$Stub$Proxy;
+Landroid/service/autofill/AutofillServiceInfo;
+Landroid/service/autofill/FillResponse;
+Landroid/service/autofill/IAutoFillService;
+Landroid/service/autofill/IAutoFillService$Stub;
+Landroid/service/autofill/IAutoFillService$Stub$Proxy;
+Landroid/service/autofill/UserData;
+Landroid/service/contentcapture/ContentCaptureServiceInfo;
+Landroid/service/contentcapture/FlushMetrics;
+Landroid/service/contentcapture/IContentCaptureService;
+Landroid/service/contentcapture/IContentCaptureService$Stub;
+Landroid/service/contentcapture/IContentCaptureService$Stub$Proxy;
+Landroid/service/dreams/IDreamService;
+Landroid/service/dreams/IDreamService$Stub;
+Landroid/service/dreams/IDreamService$Stub$Proxy;
+[Landroid/service/euicc/EuiccProfileInfo;
+Landroid/service/gatekeeper/GateKeeperResponse;
+[Landroid/service/notification/Condition;
+Landroid/service/notification/IConditionProvider$Stub$Proxy;
+Landroid/service/notification/INotificationListener$Stub$Proxy;
+Landroid/service/notification/ScheduleCalendar;
+Landroid/service/notification/SnoozeCriterion;
+[Landroid/service/notification/StatusBarNotification;
+[Landroid/service/notification/ZenModeConfig$ZenRule;
+Landroid/service/persistentdata/IPersistentDataBlockService$Stub$Proxy;
+Landroid/service/textclassifier/ITextClassifierCallback$Stub;
+Landroid/service/textclassifier/ITextClassifierCallback$Stub$Proxy;
+Landroid/service/trust/ITrustAgentService;
+Landroid/service/trust/ITrustAgentService$Stub;
+Landroid/service/trust/ITrustAgentService$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionService$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionSession$Stub;
+Landroid/service/voice/IVoiceInteractionSession$Stub$Proxy;
+Landroid/service/voice/IVoiceInteractionSessionService$Stub$Proxy;
+Landroid/service/voice/VoiceInteractionServiceInfo;
+Landroid/service/vr/IPersistentVrStateCallbacks$Stub$Proxy;
+Landroid/service/vr/IVrManager$Stub$Proxy;
+Landroid/service/vr/IVrStateCallbacks$Stub$Proxy;
+Landroid/service/wallpaper/IWallpaperEngine$Stub$Proxy;
+Landroid/service/wallpaper/IWallpaperService$Stub$Proxy;
+Landroid/stats/devicepolicy/nano/StringList;
+Landroid/sysprop/ProductProperties;
+[Landroid/system/StructPollfd;
+Landroid/system/suspend/WakeLockInfo;
+Landroid/telecom/-$$Lambda$cyYWqCYT05eM23eLVm4oQ5DrYjw;
+Landroid/telecom/-$$Lambda$qa4s1Fm2YuohEunaJUJcmJXDXG0;
+Landroid/telecom/AudioState;
+Landroid/telecom/Conference;
+Landroid/telecom/Conferenceable;
+Landroid/telecom/Connection;
+Landroid/telecom/Connection$FailureSignalingConnection;
+Landroid/telecom/ConnectionRequest;
+Landroid/telecom/ConnectionService$1;
+Landroid/telecom/ConnectionService$2;
+Landroid/telecom/ConnectionService$3;
+Landroid/telecom/ConnectionService$4;
+Landroid/telecom/ConnectionService$5;
+Landroid/telecom/ConnectionServiceAdapter;
+Landroid/telecom/DisconnectCause;
+Landroid/telecom/Logging/-$$Lambda$L5F_SL2jOCUETYvgdB36aGwY50E;
+Landroid/telecom/Logging/-$$Lambda$SessionManager$hhtZwTEbvO-fLNlAvB6Do9_2gW4;
+Landroid/telecom/Logging/-$$Lambda$SessionManager$VyH2gT1EjIvzDy_C9JfTT60CISM;
+Landroid/telecom/Logging/EventManager;
+Landroid/telecom/Logging/EventManager$Event;
+Landroid/telecom/Logging/EventManager$EventListener;
+Landroid/telecom/Logging/EventManager$EventRecord;
+Landroid/telecom/Logging/Runnable;
+Landroid/telecom/Logging/Runnable$1;
+Landroid/telecom/Logging/Session;
+Landroid/telecom/Logging/Session$Info;
+Landroid/telecom/Logging/SessionManager;
+Landroid/telecom/Logging/SessionManager$ISessionListener;
+Landroid/telecom/ParcelableConference;
+Landroid/telecom/ParcelableConnection;
+Landroid/telecom/RemoteConnectionManager;
+Landroid/telecom/StatusHints;
+Landroid/telecom/VideoProfile;
+Landroid/telephony/-$$Lambda$DataFailCause$djkZSxdG-s-w2L5rQKiGu6OudyY;
+Landroid/telephony/-$$Lambda$MLKtmRGKP3e0WU7x_KyS5-Vg8q4;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$0s34qsuHFsa43jUHrTkD62ni6Ds;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$2cMrwdqnKBpixpApeIX38rmRLak;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$2VMO21pFQN-JN3kpn6vQN1zPFEU;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$4NHt5Shg_DHV-T1IxfcQLHP5-j0;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$aysbwPqxcLV_5w6LP0TzZu2D-ew;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$bELzxgwsPigyVKYkAXBO2BjcSm8;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$dUc3j82sK9P9Zpaq-91n9bk_Rpc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$Hbn6-eZxY2p3rjOfStodI04A8E8;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$ipH9N0fJiGE9EBJHahQeXcCZXzo;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$JalixlMNdjktPsNntP_JT9pymhs;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$jlNX9JiqGSNg9W49vDcKucKdeCI;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$nnG75RvQ1_1KZGJk1ySeCH1JJRg;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$Q2A8FgYlU8_D6PD78tThGut_rTc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$XyayAGWQZC2dNjwr697SfSGBBOc;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$yvQnAlFGg5EWDG2vcA9X-4xnalA;
+Landroid/telephony/-$$Lambda$PhoneStateListener$IPhoneStateListenerStub$YY3srkIkMm8vTSFJZHoiKzUUrGs;
+Landroid/telephony/AccessNetworkConstants$AccessNetworkType;
+Landroid/telephony/cdma/CdmaCellLocation;
+Landroid/telephony/CellConfigLte;
+Landroid/telephony/CellIdentityCdma;
+Landroid/telephony/CellIdentityGsm;
+Landroid/telephony/CellIdentityNr;
+Landroid/telephony/CellIdentityTdscdma;
+Landroid/telephony/CellInfoCdma;
+Landroid/telephony/CellInfoGsm;
+Landroid/telephony/CellInfoNr;
+Landroid/telephony/CellInfoTdscdma;
+Landroid/telephony/CellInfoWcdma;
+Landroid/telephony/DataConnectionRealTimeInfo;
+[Landroid/telephony/data/DataProfile;
+Landroid/telephony/data/DataService$DataCallListChangedIndication;
+Landroid/telephony/data/DataService$DeactivateDataCallRequest;
+Landroid/telephony/DataFailCause$1;
+Landroid/telephony/data/IDataService$Stub$Proxy;
+Landroid/telephony/ICellInfoCallback$Stub;
+Landroid/telephony/ICellInfoCallback$Stub$Proxy;
+Landroid/telephony/IFinancialSmsCallback$Stub;
+Landroid/telephony/IFinancialSmsCallback$Stub$Proxy;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$8xq93ap6i0L56Aegaj-ZEUt9ISc;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$AhnK6VJjwgpDNC1GXRrwfgtYvkM;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$iuI3HyNU5eUABA_QRyzQ8Jw2-8g;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$J4VhgcUtd6SivHcdkzpurbTuyLc;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$jAP4lCkBQEdyrlgt5jaNPTlFXlY;
+Landroid/telephony/ims/-$$Lambda$ImsMmTelManager$RegistrationCallback$RegistrationBinder$oDp7ilyKfflFThUCP4Du9EYoDoQ;
+Landroid/telephony/ims/aidl/IImsCapabilityCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsConfig$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsConfigCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsMmTelFeature$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsMmTelListener$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsRegistrationCallback$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsServiceController$Stub$Proxy;
+Landroid/telephony/ims/aidl/IImsSmsListener$Stub$Proxy;
+[Landroid/telephony/ims/ImsCallForwardInfo;
+Landroid/telephony/ims/ImsExternalCallState;
+[Landroid/telephony/ims/ImsSsInfo;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$cWwTXSDsk-bWPbsDJYI--DUBMnE;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$sbjuTvW-brOSWMR74UInSZEIQB0;
+Landroid/telephony/ims/stub/-$$Lambda$ImsRegistrationImplBase$wwtkoeOtGwMjG5I0-ZTfjNpGU-s;
+Landroid/telephony/INetworkService$Stub$Proxy;
+Landroid/telephony/JapanesePhoneNumberFormatter;
+Landroid/telephony/NeighboringCellInfo;
+Landroid/telephony/PhysicalChannelConfig$Builder;
+[Landroid/telephony/RadioAccessFamily;
+Landroid/telephony/SmsMessage;
+Landroid/telephony/SubscriptionManager$OnOpportunisticSubscriptionsChangedListener;
+Landroid/telephony/SubscriptionManager$OnOpportunisticSubscriptionsChangedListener$1;
+[Landroid/telephony/SubscriptionPlan;
+Landroid/telephony/SubscriptionPlan;
+[Landroid/telephony/UiccAccessRule;
+Landroid/telephony/VisualVoicemailSmsFilterSettings$Builder;
+Landroid/text/CharSequenceCharacterIterator;
+[Landroid/text/DynamicLayout$ChangeWatcher;
+[Landroid/text/FontConfig$Alias;
+[Landroid/text/FontConfig$Family;
+[Landroid/text/FontConfig$Font;
+Landroid/text/format/Formatter$BytesResult;
+Landroid/text/HtmlToSpannedConverter$Big;
+Landroid/text/HtmlToSpannedConverter$Blockquote;
+Landroid/text/HtmlToSpannedConverter$Bold;
+Landroid/text/HtmlToSpannedConverter$Bullet;
+Landroid/text/HtmlToSpannedConverter$Heading;
+Landroid/text/HtmlToSpannedConverter$Italic;
+Landroid/text/HtmlToSpannedConverter$Monospace;
+Landroid/text/HtmlToSpannedConverter$Small;
+Landroid/text/HtmlToSpannedConverter$Strikethrough;
+Landroid/text/HtmlToSpannedConverter$Sub;
+Landroid/text/HtmlToSpannedConverter$Super;
+Landroid/text/HtmlToSpannedConverter$Underline;
+[Landroid/text/Layout$Directions;
+Landroid/text/Layout$HorizontalMeasurementProvider;
+Landroid/text/Layout$TabStops;
+Landroid/text/method/DateKeyListener;
+Landroid/text/method/DateTimeKeyListener;
+Landroid/text/method/DialerKeyListener;
+Landroid/text/method/DigitsKeyListener;
+Landroid/text/method/TimeKeyListener;
+[Landroid/text/PrecomputedText$ParagraphInfo;
+Landroid/text/PrecomputedText$Params$Builder;
+[Landroid/text/Selection$MemoryTextWatcher;
+[Landroid/text/SpanWatcher;
+Landroid/text/style/AccessibilityClickableSpan;
+Landroid/text/style/AccessibilityURLSpan;
+[Landroid/text/style/AlignmentSpan;
+Landroid/text/style/AlignmentSpan$Standard;
+Landroid/text/style/BulletSpan;
+[Landroid/text/style/CharacterStyle;
+[Landroid/text/style/ClickableSpan;
+[Landroid/text/style/LeadingMarginSpan;
+Landroid/text/style/LeadingMarginSpan$LeadingMarginSpan2;
+Landroid/text/style/LeadingMarginSpan$Standard;
+[Landroid/text/style/LineBackgroundSpan;
+Landroid/text/style/LineBackgroundSpan$Standard;
+[Landroid/text/style/LineHeightSpan;
+Landroid/text/style/LineHeightSpan$Standard;
+Landroid/text/style/LineHeightSpan$WithDensity;
+Landroid/text/style/LocaleSpan;
+[Landroid/text/style/MetricAffectingSpan;
+Landroid/text/style/QuoteSpan;
+[Landroid/text/style/ReplacementSpan;
+Landroid/text/style/ScaleXSpan;
+[Landroid/text/style/SpellCheckSpan;
+Landroid/text/style/StrikethroughSpan;
+Landroid/text/style/SubscriptSpan;
+Landroid/text/style/SuggestionRangeSpan;
+[Landroid/text/style/SuggestionSpan;
+Landroid/text/style/SuperscriptSpan;
+[Landroid/text/style/TabStopSpan;
+Landroid/text/style/TtsSpan;
+Landroid/text/style/TtsSpan$TelephoneBuilder;
+Landroid/text/style/TypefaceSpan;
+[Landroid/text/TextWatcher;
+Landroid/text/util/Linkify;
+Landroid/transition/ArcMotion;
+Landroid/transition/ChangeScroll;
+Landroid/transition/CircularPropagation;
+Landroid/transition/Explode;
+Landroid/transition/PatternPathMotion;
+Landroid/transition/Recolor;
+Landroid/transition/Slide;
+Landroid/util/apk/ApkSigningBlockUtils$1;
+[Landroid/util/apk/DataSource;
+Landroid/util/apk/VerityBuilder$BufferedDigester;
+Landroid/util/apk/VerityBuilder$VerityResult;
+[Landroid/util/ArrayMap;
+Landroid/util/BackupUtils;
+Landroid/util/Half;
+Landroid/util/JsonWriter$1;
+Landroid/util/KeyValueSettingObserver;
+Landroid/util/KeyValueSettingObserver$SettingObserver;
+Landroid/util/LauncherIcons;
+Landroid/util/LogWriter;
+Landroid/util/LongArrayQueue;
+Landroid/util/proto/EncodedBuffer;
+Landroid/util/RecurrenceRule;
+Landroid/util/RecurrenceRule$NonrecurringIterator;
+Landroid/util/RecurrenceRule$RecurringIterator;
+Landroid/util/Spline$LinearSpline;
+Landroid/util/UtilConfig;
+Landroid/view/-$$Lambda$1kvF4JuyM42-wmyDVPAIYdPz1jE;
+Landroid/view/-$$Lambda$cZhmLzK8aetUdx4VlP9w5jR7En0;
+Landroid/view/-$$Lambda$dj1hfDQd0iEp_uBDBPEUMMYJJwk;
+Landroid/view/-$$Lambda$PYGleuqIeCxjTD1pJqqx1opFv1g;
+Landroid/view/-$$Lambda$SurfaceView$w68OV7dB_zKVNsA-r0IrAUtyWas;
+Landroid/view/-$$Lambda$ThreadedRenderer$ydBD-R1iP5u-97XYakm-jKvC1b4;
+Landroid/view/-$$Lambda$ViewRootImpl$dznxCZGM2R1fsBljsJKomLjBRoM;
+Landroid/view/-$$Lambda$ViewRootImpl$IReiNMSbDakZSGbIZuL_ifaFWn8;
+Landroid/view/-$$Lambda$ViewRootImpl$YBiqAhbCbXVPSKdbE3K4rH2gpxI;
+Landroid/view/-$$Lambda$ViewRootImpl$zlBUjCwDtoAWMNaHI62DIq-eKFY;
+Landroid/view/-$$Lambda$WindowManagerGlobal$2bR3FsEm4EdRwuXfttH0wA2xOW4;
+Landroid/view/-$$Lambda$WlJa6OPA72p3gYtA3nVKC7Z1tGY;
+Landroid/view/accessibility/-$$Lambda$AccessibilityManager$1$o7fCplskH9NlBwJvkl6NoZ0L_BA;
+Landroid/view/accessibility/-$$Lambda$AccessibilityManager$yzw5NYY7_MfAQ9gLy3mVllchaXo;
+Landroid/view/accessibility/IAccessibilityInteractionConnection$Stub$Proxy;
+Landroid/view/accessibility/IAccessibilityManagerClient$Stub$Proxy;
+Landroid/view/accessibility/WeakSparseArray;
+Landroid/view/accessibility/WeakSparseArray$WeakReferenceWithId;
+Landroid/view/animation/AnticipateInterpolator;
+Landroid/view/animation/AnticipateOvershootInterpolator;
+Landroid/view/animation/BounceInterpolator;
+Landroid/view/animation/ClipRectAnimation;
+Landroid/view/animation/CycleInterpolator;
+Landroid/view/animation/GridLayoutAnimationController;
+Landroid/view/animation/RotateAnimation;
+[Landroid/view/AppTransitionAnimationSpec;
+Landroid/view/AppTransitionAnimationSpec;
+Landroid/view/autofill/IAutoFillManagerClient$Stub$Proxy;
+[Landroid/view/Choreographer$CallbackQueue;
+Landroid/view/contentcapture/ContentCaptureCondition;
+Landroid/view/contentcapture/ContentCaptureContext;
+Landroid/view/contentcapture/DataRemovalRequest;
+[Landroid/view/Display;
+Landroid/view/DragEvent;
+Landroid/view/GestureExclusionTracker$GestureExclusionViewInfo;
+[Landroid/view/HandlerActionQueue$HandlerAction;
+Landroid/view/IAppTransitionAnimationSpecsFuture$Stub;
+Landroid/view/IAppTransitionAnimationSpecsFuture$Stub$Proxy;
+Landroid/view/IDisplayFoldListener$Stub;
+Landroid/view/IDisplayFoldListener$Stub$Proxy;
+Landroid/view/IDockedStackListener$Stub$Proxy;
+Landroid/view/IGraphicsStatsCallback$Stub$Proxy;
+Landroid/view/inputmethod/-$$Lambda$InputMethodManager$dfnCauFoZCf-HfXs1QavrkwWDf0;
+Landroid/view/inputmethod/-$$Lambda$InputMethodManager$iDWn3IGSUFqIcs8Py42UhfrshxI;
+[Landroid/view/inputmethod/CompletionInfo;
+Landroid/view/inputmethod/CompletionInfo;
+Landroid/view/inputmethod/CorrectionInfo;
+Landroid/view/inputmethod/CursorAnchorInfo;
+Landroid/view/inputmethod/ExtractedTextRequest;
+Landroid/view/inputmethod/InputContentInfo;
+Landroid/view/inputmethod/InputMethod;
+Landroid/view/inputmethod/InputMethodSession;
+[Landroid/view/inputmethod/InputMethodSubtype;
+[Landroid/view/InsetsSourceControl;
+Landroid/view/InsetsSourceControl;
+Landroid/view/IOnKeyguardExitResult$Stub;
+Landroid/view/IOnKeyguardExitResult$Stub$Proxy;
+Landroid/view/IPinnedStackController$Stub$Proxy;
+Landroid/view/IPinnedStackListener$Stub$Proxy;
+Landroid/view/IRecentsAnimationRunner;
+Landroid/view/IRecentsAnimationRunner$Stub;
+Landroid/view/IRecentsAnimationRunner$Stub$Proxy;
+Landroid/view/IRemoteAnimationRunner$Stub;
+Landroid/view/IRemoteAnimationRunner$Stub$Proxy;
+Landroid/view/IRotationWatcher$Stub$Proxy;
+Landroid/view/ISystemGestureExclusionListener$Stub;
+Landroid/view/ISystemGestureExclusionListener$Stub$Proxy;
+Landroid/view/IWindow$Stub$Proxy;
+Landroid/view/IWindowSessionCallback$Stub$Proxy;
+Landroid/view/KeyCharacterMap$UnavailableException;
+Landroid/view/LayoutInflater$BlinkLayout;
+Landroid/view/LayoutInflater$BlinkLayout$1;
+Landroid/view/MenuInflater$InflatedOnMenuItemClickListener;
+[Landroid/view/MotionEvent$PointerCoords;
+[Landroid/view/MotionEvent$PointerProperties;
+Landroid/view/PointerIcon$2;
+Landroid/view/RemotableViewMethod;
+Landroid/view/RemoteAnimationDefinition;
+Landroid/view/RemoteAnimationDefinition$RemoteAnimationAdapterEntry;
+Landroid/view/RenderNodeAnimator$DelayedAnimationHelper;
+Landroid/view/RoundScrollbarRenderer;
+Landroid/view/SoundEffectConstants;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$6oTtcn9bDE-u-8FbiyGdntqoQG0;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$sY0w9od2zcl4YFel0lG4VB3vf7I;
+Landroid/view/textclassifier/-$$Lambda$ActionsSuggestionsHelper$YTQv8oPvlmJL4tITUFD4z4JWKRk;
+Landroid/view/textclassifier/-$$Lambda$OGSS2qx6njxlnp0dnKb4lA3jnw8;
+Landroid/view/textclassifier/-$$Lambda$TextClassificationManager$oweIEhDWxy3_0kZSXp3oRbSuNW4;
+Landroid/view/textclassifier/-$$Lambda$TextClassificationManager$SIydN2POphTO3AmPTLEMmXPLSKY;
+Landroid/view/textclassifier/-$$Lambda$TextClassifierImpl$ftq-sQqJYwUdrdbbr9jz3p4AWos;
+Landroid/view/textclassifier/-$$Lambda$TextClassifierImpl$iSt_Guet-O6Vtdk0MA4z-Z4lzaM;
+Landroid/view/textclassifier/ActionsModelParamsSupplier;
+Landroid/view/textclassifier/ActionsModelParamsSupplier$ActionsModelParams;
+Landroid/view/textclassifier/ActionsSuggestionsHelper;
+Landroid/view/textclassifier/ActionsSuggestionsHelper$PersonEncoder;
+Landroid/view/textclassifier/ConversationAction;
+Landroid/view/textclassifier/ConversationAction$Builder;
+Landroid/view/textclassifier/ConversationActions$Message;
+Landroid/view/textclassifier/ConversationActions$Request;
+Landroid/view/textclassifier/ExtrasUtils;
+Landroid/view/textclassifier/intent/LabeledIntent;
+Landroid/view/textclassifier/intent/LabeledIntent$Result;
+Landroid/view/textclassifier/Log;
+Landroid/view/textclassifier/ModelFileManager$ModelFile;
+Landroid/view/textclassifier/SelectionEvent;
+Landroid/view/textclassifier/SelectionSessionLogger$SignatureParser;
+Landroid/view/textclassifier/SystemTextClassifier$BlockingCallback;
+Landroid/view/textclassifier/SystemTextClassifier$ResponseReceiver;
+Landroid/view/textclassifier/TextClassification$Request;
+Landroid/view/textclassifier/TextClassificationContext;
+Landroid/view/textclassifier/TextClassificationContext$Builder;
+Landroid/view/textclassifier/TextClassificationSessionId;
+Landroid/view/textclassifier/TextClassifier$EntityConfig;
+Landroid/view/textclassifier/TextClassifier$EntityConfig$Builder;
+Landroid/view/textclassifier/TextClassifier$Utils;
+Landroid/view/textclassifier/TextClassifierEvent;
+Landroid/view/textclassifier/TextClassifierEvent$ConversationActionsEvent;
+Landroid/view/textclassifier/TextClassifierEvent$LanguageDetectionEvent;
+Landroid/view/textclassifier/TextClassifierEvent$TextLinkifyEvent;
+Landroid/view/textclassifier/TextClassifierEvent$TextSelectionEvent;
+Landroid/view/textclassifier/TextLanguage;
+Landroid/view/textclassifier/TextLanguage$Builder;
+Landroid/view/textclassifier/TextLanguage$Request;
+Landroid/view/textclassifier/TextLanguage$Request$Builder;
+Landroid/view/textclassifier/TextLinks$Request;
+Landroid/view/textclassifier/TextSelection$Request;
+[Landroid/view/View;
+[Landroid/view/View$AttachInfo$InvalidateInfo;
+Landroid/view/View$AttachInfo$InvalidateInfo;
+Landroid/view/View$CheckForLongPress;
+Landroid/view/View$DeclaredOnClickListener;
+Landroid/view/ViewGroup$ChildListForAccessibility;
+Landroid/view/ViewGroup$ChildListForAutoFillOrContentCapture;
+Landroid/view/ViewPropertyAnimator$2;
+Landroid/view/ViewPropertyAnimator$3;
+Landroid/view/ViewRootImpl$2;
+Landroid/view/ViewRootImpl$3;
+Landroid/view/ViewRootImpl$CalledFromWrongThreadException;
+Landroid/view/ViewRootImpl$TakenSurfaceHolder;
+Landroid/view/ViewStructure$HtmlInfo;
+Landroid/view/ViewStub$ViewReplaceRunnable;
+Landroid/view/ViewTreeObserver$OnEnterAnimationCompleteListener;
+Landroid/view/ViewTreeObserver$OnWindowAttachListener;
+Landroid/view/ViewTreeObserver$OnWindowFocusChangeListener;
+Landroid/view/ViewTreeObserver$OnWindowShownListener;
+Landroid/view/WindowInsets$Builder;
+Landroid/view/WindowManager$InvalidDisplayException;
+Landroid/widget/-$$Lambda$RemoteViews$SetOnClickResponse$9rKnU2QqCzJhBC39ZrKYXob0-MA;
+Landroid/widget/AbsListView$1;
+Landroid/widget/AbsListView$2;
+Landroid/widget/AbsListView$4;
+Landroid/widget/AbsListView$CheckForTap;
+Landroid/widget/AbsListView$ListItemAccessibilityDelegate;
+Landroid/widget/AbsListView$SelectionBoundsAdjuster;
+Landroid/widget/ActionMenuPresenter$ActionMenuPopupCallback;
+Landroid/widget/ActionMenuView$ActionMenuPresenterCallback;
+Landroid/widget/Chronometer;
+Landroid/widget/Chronometer$1;
+Landroid/widget/Editor$EasyEditPopupWindow;
+Landroid/widget/Editor$SpanController$1;
+Landroid/widget/Editor$SpanController$2;
+[Landroid/widget/Editor$TextRenderNode;
+[Landroid/widget/Editor$TextViewPositionListener;
+Landroid/widget/GridLayout;
+Landroid/widget/GridLayout$7$1;
+[[Landroid/widget/GridLayout$Arc;
+[Landroid/widget/GridLayout$Arc;
+Landroid/widget/GridLayout$Axis;
+Landroid/widget/GridLayout$Axis$1;
+[Landroid/widget/GridLayout$Bounds;
+[Landroid/widget/GridLayout$Interval;
+Landroid/widget/GridLayout$LayoutParams;
+[Landroid/widget/GridLayout$MutableInt;
+[Landroid/widget/GridLayout$Spec;
+Landroid/widget/GridLayout$Spec;
+Landroid/widget/ImageView$ImageDrawableCallback;
+Landroid/widget/ListView$FocusSelector;
+Landroid/widget/PopupMenu$1;
+Landroid/widget/PopupMenu$2;
+Landroid/widget/PopupWindow$3;
+Landroid/widget/ProgressBar$ProgressTintInfo;
+Landroid/widget/ProgressBar$RefreshData;
+Landroid/widget/ProgressBar$RefreshProgressRunnable;
+Landroid/widget/RemoteViews$ActionException;
+Landroid/widget/RemoteViews$AsyncApplyTask;
+Landroid/widget/RemoteViews$MethodArgs;
+Landroid/widget/RemoteViews$OverrideTextColorsAction;
+Landroid/widget/RemoteViews$RemoteViewsContextWrapper;
+Landroid/widget/RemoteViews$RunnableAction;
+Landroid/widget/RemoteViews$SetEmptyView;
+Landroid/widget/RemoteViews$SetIntTagAction;
+Landroid/widget/RemoteViews$SetPendingIntentTemplate;
+Landroid/widget/RemoteViews$SetRemoteInputsAction;
+Landroid/widget/RemoteViews$SetRemoteViewsAdapterIntent;
+Landroid/widget/RemoteViews$SetRemoteViewsAdapterList;
+Landroid/widget/RemoteViews$SetRippleDrawableColor;
+Landroid/widget/RemoteViews$TextViewDrawableAction;
+Landroid/widget/RemoteViews$TextViewSizeAction;
+Landroid/widget/RemoteViews$ViewContentNavigation;
+Landroid/widget/RemoteViews$ViewGroupActionAdd;
+Landroid/widget/RemoteViews$ViewGroupActionRemove;
+[Landroid/widget/SpellChecker$SpellParser;
+Landroid/widget/Switch;
+Landroid/widget/TextView$1;
+[Landroid/widget/TextView$ChangeWatcher;
+Landroid/widget/TextView$Marquee;
+Landroid/widget/Toast$TN;
+Landroid/widget/Toast$TN$1;
+Landroid/widget/Toolbar$SavedState;
+Lcom/android/framework/protobuf/nano/CodedInputByteBufferNano;
+Lcom/android/framework/protobuf/nano/CodedOutputByteBufferNano;
+Lcom/android/framework/protobuf/nano/CodedOutputByteBufferNano$OutOfSpaceException;
+Lcom/android/framework/protobuf/nano/WireFormatNano;
+Lcom/android/i18n/phonenumbers/geocoding/PhoneNumberOfflineGeocoder;
+Lcom/android/i18n/phonenumbers/NumberParseException$ErrorType;
+Lcom/android/i18n/phonenumbers/Phonemetadata$NumberFormat$Builder;
+Lcom/android/i18n/phonenumbers/ShortNumberInfo;
+Lcom/android/ims/-$$Lambda$ImsManager$_6YCQyhjHBSdrm4ZBEMUQ2AAqOY;
+Lcom/android/ims/ImsCall;
+Lcom/android/ims/ImsManager$2;
+Lcom/android/ims/internal/uce/common/UceLong;
+Lcom/android/ims/internal/uce/options/IOptionsListener$Stub;
+Lcom/android/ims/internal/uce/options/IOptionsListener$Stub$Proxy;
+Lcom/android/ims/internal/uce/presence/IPresenceListener$Stub;
+Lcom/android/ims/internal/uce/presence/IPresenceListener$Stub$Proxy;
+Lcom/android/ims/internal/uce/UceServiceBase$UceServiceBinder;
+Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;
+Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub$Proxy;
+Lcom/android/internal/accessibility/AccessibilityShortcutController$ToggleableFrameworkFeatureInfo;
+Lcom/android/internal/app/AlertController$1;
+Lcom/android/internal/app/AlertController$ButtonHandler;
+Lcom/android/internal/app/AlertController$RecycleListView;
+Lcom/android/internal/app/IAppOpsActiveCallback$Stub$Proxy;
+Lcom/android/internal/app/IAppOpsNotedCallback$Stub$Proxy;
+Lcom/android/internal/app/IVoiceActionCheckCallback$Stub;
+Lcom/android/internal/app/IVoiceActionCheckCallback$Stub$Proxy;
+Lcom/android/internal/app/IVoiceInteractionSessionListener$Stub$Proxy;
+Lcom/android/internal/app/IVoiceInteractionSessionShowCallback$Stub;
+Lcom/android/internal/app/IVoiceInteractor$Stub$Proxy;
+Lcom/android/internal/app/MicroAlertController;
+Lcom/android/internal/app/procstats/DumpUtils;
+Lcom/android/internal/app/procstats/IProcessStats$Stub$Proxy;
+Lcom/android/internal/app/procstats/ProcessState$PssAggr;
+Lcom/android/internal/app/procstats/ProcessStats$TotalMemoryUseCollection;
+Lcom/android/internal/app/ResolverActivity$ActionTitle;
+Lcom/android/internal/app/ToolbarActionBar;
+Lcom/android/internal/appwidget/IAppWidgetHost$Stub;
+Lcom/android/internal/appwidget/IAppWidgetHost$Stub$Proxy;
+Lcom/android/internal/app/WindowDecorActionBar;
+Lcom/android/internal/backup/IBackupTransport$Stub$Proxy;
+Lcom/android/internal/content/PackageHelper$1;
+Lcom/android/internal/graphics/ColorUtils;
+Lcom/android/internal/infra/-$$Lambda$7-CJJfrUZBVuXZyYFEWBNh8Mky8;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$6FcEKfZ-7TXLg6dcCU8EMuMNAy4;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$9IBVTCLLZgndvH7fu1P14PW1_1o;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$ocrHd68Md9x6FfAzVQ6w8MAjFqY;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$PendingRequest$IBoaBGXZQEXJr69u3aJF-LCJ42Y;
+Lcom/android/internal/infra/-$$Lambda$AbstractRemoteService$YSUzqqi1Pbrg2dlwMGMtKWbGXck;
+Lcom/android/internal/infra/-$$Lambda$EbzSql2RHkXox5Myj8A-7kLC4_A;
+Lcom/android/internal/infra/AbstractRemoteService$MyAsyncPendingRequest;
+Lcom/android/internal/inputmethod/IInputMethodPrivilegedOperations$Stub$Proxy;
+Lcom/android/internal/inputmethod/InputMethodDebug;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperations;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperations$OpsHolder;
+Lcom/android/internal/inputmethod/InputMethodPrivilegedOperationsRegistry;
+Lcom/android/internal/inputmethod/SubtypeLocaleUtils;
+Lcom/android/internal/location/ILocationProvider;
+Lcom/android/internal/location/ILocationProvider$Stub;
+Lcom/android/internal/location/ILocationProvider$Stub$Proxy;
+Lcom/android/internal/location/ILocationProviderManager$Stub$Proxy;
+Lcom/android/internal/location/nano/GnssLogsProto$GnssLog;
+Lcom/android/internal/location/nano/GnssLogsProto$PowerMetrics;
+Lcom/android/internal/net/INetworkWatchlistManager$Stub$Proxy;
+Lcom/android/internal/net/VpnConfig;
+[Lcom/android/internal/net/VpnInfo;
+Lcom/android/internal/net/VpnProfile;
+Lcom/android/internal/os/-$$Lambda$BinderCallsStats$sqXweH5BoxhmZvI188ctqYiACRk;
+Lcom/android/internal/os/-$$Lambda$sHtqZgGVjxOf9IJdAdZO6gwD_Do;
+Lcom/android/internal/os/BatteryStatsHelper$1;
+Lcom/android/internal/os/BatteryStatsImpl$3;
+Lcom/android/internal/os/BatteryStatsImpl$4;
+[Lcom/android/internal/os/BatteryStatsImpl$Counter;
+[[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounter;
+[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounter;
+[Lcom/android/internal/os/BatteryStatsImpl$LongSamplingCounterArray;
+[Lcom/android/internal/os/BatteryStatsImpl$StopwatchTimer;
+Lcom/android/internal/os/BatteryStatsImpl$UidToRemove;
+Lcom/android/internal/os/BinderCallsStats$CallStatKey;
+Lcom/android/internal/os/BinderCallsStats$ExportedCallStat;
+Lcom/android/internal/os/BinderDeathDispatcher$RecipientsInfo;
+Lcom/android/internal/os/BinderInternal$CallSession;
+Lcom/android/internal/os/IShellCallback$Stub;
+Lcom/android/internal/os/IShellCallback$Stub$Proxy;
+[Lcom/android/internal/os/KernelCpuSpeedReader;
+Lcom/android/internal/os/KernelCpuThreadReader$ProcessCpuUsage;
+Lcom/android/internal/os/KernelCpuThreadReader$ThreadCpuUsage;
+Lcom/android/internal/os/LooperStats$ExportedEntry;
+[Lcom/android/internal/os/PowerProfile$CpuClusterKey;
+Lcom/android/internal/os/StatsdHiddenApiUsageLogger;
+Lcom/android/internal/os/TransferPipe;
+Lcom/android/internal/os/WifiPowerEstimator;
+Lcom/android/internal/os/WrapperInit;
+Lcom/android/internal/os/ZygoteSecurityException;
+Lcom/android/internal/os/ZygoteServer$UsapPoolRefillAction;
+Lcom/android/internal/policy/DecorView$2;
+Lcom/android/internal/policy/DecorView$3;
+Lcom/android/internal/policy/IKeyguardDismissCallback$Stub;
+Lcom/android/internal/policy/IKeyguardDismissCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardDrawnCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardExitCallback$Stub;
+Lcom/android/internal/policy/IKeyguardExitCallback$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardService$Stub$Proxy;
+Lcom/android/internal/policy/IKeyguardStateCallback$Stub$Proxy;
+Lcom/android/internal/policy/IShortcutService$Stub$Proxy;
+[Lcom/android/internal/policy/PhoneWindow$PanelFeatureState;
+Lcom/android/internal/policy/PhoneWindow$PanelFeatureState$SavedState;
+Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
+Lcom/android/internal/statusbar/IStatusBarService$Stub$Proxy;
+[Lcom/android/internal/statusbar/NotificationVisibility;
+Lcom/android/internal/statusbar/NotificationVisibility;
+[Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation;
+Lcom/android/internal/statusbar/NotificationVisibility$NotificationLocation;
+Lcom/android/internal/telecom/IConnectionService;
+Lcom/android/internal/telecom/IConnectionService$Stub;
+Lcom/android/internal/telecom/IConnectionService$Stub$Proxy;
+Lcom/android/internal/telecom/IInCallService;
+Lcom/android/internal/telecom/IInCallService$Stub;
+Lcom/android/internal/telecom/IInCallService$Stub$Proxy;
+Lcom/android/internal/telecom/IVideoProvider$Stub$Proxy;
+Lcom/android/internal/telecom/RemoteServiceCallback$Stub;
+Lcom/android/internal/telecom/RemoteServiceCallback$Stub$Proxy;
+Lcom/android/internal/telephony/-$$Lambda$MultiSimSettingController$55347QtGjuukX-px3jYZkJd_z3U;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$1TnOMFYcM13ZTJNoLjxguPwVcxw;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$2xgrYNleR8FFzFT8hEQx3mDtZ8g;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$EYZUPU0CYhRoptGCGJ9y78u-jQM;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$Ja9yTBcEYPqTRBIP-hL0otixVeE;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$PONge0j2mBi_ILbtJD_7euF0uoM;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$rpyQeO7zACcc5v4krwU9_qRMHL8;
+Lcom/android/internal/telephony/-$$Lambda$PhoneSubInfoController$ZOtVAnuhxrXl2L906I6eTOentP0;
+Lcom/android/internal/telephony/-$$Lambda$RadioIndication$GND6XxOOm1d_Ro76zEUFjA9OrEA;
+Lcom/android/internal/telephony/-$$Lambda$RIL$803u4JiCud_JSoDndvAhT13ZZqU;
+Lcom/android/internal/telephony/-$$Lambda$RIL$Ir4pOMTf7R0Jtw4O3F7JgMVtXO4;
+Lcom/android/internal/telephony/-$$Lambda$RIL$ZGWeCQ9boMO1_J1_yQ82l_jK-Nc;
+Lcom/android/internal/telephony/-$$Lambda$RIL$zYsQZAc3z9bM5fCaq_J0dn5kjjo;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionController$0y_j8vef67bMEiPQdeWyjuFpPQ8;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$DY4i_CG7hrAeejGLeh3hMUZySnw;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$ecTEeMEIjOEa2z5W3wjqiicibbY;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$qyDxq2AWyReUxdc6HttVGQeDD3Y;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$tLUuQ7lYu8EjRd038qzQlDm-CtA;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$UFyB0ValfLD0rdGDibCjTnGFkeo;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$Y5woGfEDKrozRViLH7WF93qPEno;
+Lcom/android/internal/telephony/-$$Lambda$SubscriptionInfoUpdater$ZTY4uxKw17CHcHQzbBUF7m-dN-E;
+Lcom/android/internal/telephony/-$$Lambda$TelephonyComponentFactory$InjectedComponents$09rMKC8001jAR0zFrzzlPx26Xjs;
+Lcom/android/internal/telephony/-$$Lambda$TelephonyComponentFactory$InjectedComponents$UYUq9z2WZwxqOLXquU0tTNN9wAs;
+Lcom/android/internal/telephony/-$$Lambda$UV1wDVoVlbcxpr8zevj_aMFtUGw;
+Lcom/android/internal/telephony/-$$Lambda$WWHOcG5P4-jgjzPPgLwm-wN15OM;
+Lcom/android/internal/telephony/BlockChecker;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler$1;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CallerInfoAsyncQueryHandler$CallerInfoWorkerHandler;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$CookieWrapper;
+Lcom/android/internal/telephony/CallerInfoAsyncQuery$QueryPoolException;
+[Lcom/android/internal/telephony/CallForwardInfo;
+[Lcom/android/internal/telephony/CarrierServiceBindHelper$AppBinding;
+Lcom/android/internal/telephony/CarrierServiceBindHelper$CarrierServiceConnection;
+Lcom/android/internal/telephony/CarrierServicesSmsFilter;
+Lcom/android/internal/telephony/CarrierServicesSmsFilter$CallbackTimeoutHandler;
+Lcom/android/internal/telephony/CarrierSmsUtils;
+Lcom/android/internal/telephony/cat/AppInterface$CommandType;
+Lcom/android/internal/telephony/cat/BerTlv;
+Lcom/android/internal/telephony/cat/BIPClientParams;
+Lcom/android/internal/telephony/cat/CallSetupParams;
+Lcom/android/internal/telephony/cat/CatCmdMessage;
+Lcom/android/internal/telephony/cat/CatResponseMessage;
+[Lcom/android/internal/telephony/cat/CatService;
+Lcom/android/internal/telephony/cat/CatService$1;
+Lcom/android/internal/telephony/cat/CommandParams;
+Lcom/android/internal/telephony/cat/CommandParamsFactory$1;
+Lcom/android/internal/telephony/cat/ComprehensionTlv;
+Lcom/android/internal/telephony/cat/ComprehensionTlvTag;
+Lcom/android/internal/telephony/cat/DisplayTextParams;
+Lcom/android/internal/telephony/cat/DTTZResponseData;
+Lcom/android/internal/telephony/cat/LanguageParams;
+Lcom/android/internal/telephony/cat/LanguageResponseData;
+Lcom/android/internal/telephony/cat/LaunchBrowserParams;
+Lcom/android/internal/telephony/cat/ResultCode;
+Lcom/android/internal/telephony/cat/RilMessage;
+[Lcom/android/internal/telephony/cat/RilMessageDecoder;
+Lcom/android/internal/telephony/cat/ValueParser;
+Lcom/android/internal/telephony/cdma/CdmaCallWaitingNotification;
+[Lcom/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo;
+Lcom/android/internal/telephony/cdma/SmsMessage;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameData;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameData$Builder;
+Lcom/android/internal/telephony/cdnr/CarrierDisplayNameResolver;
+Lcom/android/internal/telephony/Connection$Listener;
+Lcom/android/internal/telephony/Connection$PostDialState;
+Lcom/android/internal/telephony/dataconnection/-$$Lambda$DataConnection$-tFSpFGzTv_UdpzJlTMOvg8VO98;
+Lcom/android/internal/telephony/dataconnection/-$$Lambda$XZAGhHrbkIDyusER4MAM6luKcT0;
+Lcom/android/internal/telephony/dataconnection/DataConnection$DisconnectParams;
+Lcom/android/internal/telephony/dataconnection/DataConnection$UpdateLinkPropertyResult;
+Lcom/android/internal/telephony/dataconnection/DataEnabledOverride;
+Lcom/android/internal/telephony/dataconnection/DataEnabledSettings$1;
+Lcom/android/internal/telephony/dataconnection/DataEnabledSettings$2;
+Lcom/android/internal/telephony/dataconnection/DcNetworkAgent;
+Lcom/android/internal/telephony/dataconnection/DcTracker$4;
+Lcom/android/internal/telephony/dataconnection/DcTracker$ProvisionNotificationBroadcastReceiver;
+Lcom/android/internal/telephony/dataconnection/KeepaliveStatus;
+[Lcom/android/internal/telephony/dataconnection/TelephonyNetworkFactory;
+Lcom/android/internal/telephony/dataconnection/TransportManager$HandoverParams;
+Lcom/android/internal/telephony/DriverCall;
+Lcom/android/internal/telephony/DriverCall$State;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$1;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$10;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$11;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$12;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$13;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$2;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$3;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$5;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$6;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$7;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$8;
+Lcom/android/internal/telephony/euicc/EuiccConnector$ConnectedState$9;
+Lcom/android/internal/telephony/euicc/EuiccConnector$DeleteRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$DownloadRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$GetDefaultListRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$GetMetadataRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$SwitchRequest;
+Lcom/android/internal/telephony/euicc/EuiccConnector$UpdateNicknameRequest;
+Lcom/android/internal/telephony/euicc/IEuiccController$Stub$Proxy;
+Lcom/android/internal/telephony/GsmCdmaCallTracker$2;
+Lcom/android/internal/telephony/GsmCdmaCallTracker$3;
+[Lcom/android/internal/telephony/GsmCdmaConnection;
+Lcom/android/internal/telephony/GsmCdmaPhone$Cfu;
+Lcom/android/internal/telephony/gsm/GsmMmiCode;
+[Lcom/android/internal/telephony/gsm/SmsBroadcastConfigInfo;
+Lcom/android/internal/telephony/gsm/SmsMessage;
+Lcom/android/internal/telephony/gsm/SuppServiceNotification;
+Lcom/android/internal/telephony/gsm/UsimPhoneBookManager$File;
+Lcom/android/internal/telephony/gsm/UsimPhoneBookManager$PbrRecord;
+Lcom/android/internal/telephony/HbpcdUtils;
+Lcom/android/internal/telephony/IccPhoneBookInterfaceManager$Request;
+Lcom/android/internal/telephony/IIccPhoneBook$Stub$Proxy;
+Lcom/android/internal/telephony/IIntegerConsumer$Stub;
+Lcom/android/internal/telephony/IIntegerConsumer$Stub$Proxy;
+Lcom/android/internal/telephony/IMms$Stub$Proxy;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$-jFhgP_NotuFSwzjQBXWuvls4x4;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$kF808g2NWzNL8H1SwzDc1FxiQdQ;
+Lcom/android/internal/telephony/ims/-$$Lambda$ImsResolver$rPjfocpARQ2sab24iic4o3kTTgw;
+Lcom/android/internal/telephony/ims/ImsResolver$8;
+Lcom/android/internal/telephony/ims/ImsServiceFeatureQueryManager$ImsServiceFeatureQuery;
+Lcom/android/internal/telephony/imsphone/ImsExternalConnection;
+Lcom/android/internal/telephony/imsphone/ImsPhone$Cf;
+Lcom/android/internal/telephony/imsphone/ImsPhoneConnection;
+Lcom/android/internal/telephony/imsphone/ImsPhoneMmiCode;
+Lcom/android/internal/telephony/ims/RcsMessageController;
+Lcom/android/internal/telephony/InboundSmsHandler$CarrierServicesSmsFilterCallback;
+Lcom/android/internal/telephony/InboundSmsHandler$SmsBroadcastReceiver;
+Lcom/android/internal/telephony/InboundSmsTracker;
+Lcom/android/internal/telephony/INumberVerificationCallback$Stub;
+Lcom/android/internal/telephony/INumberVerificationCallback$Stub$Proxy;
+Lcom/android/internal/telephony/IPhoneStateListener$Stub$Proxy;
+Lcom/android/internal/telephony/ISetOpportunisticDataCallback$Stub$Proxy;
+Lcom/android/internal/telephony/ISms$Stub$Proxy;
+Lcom/android/internal/telephony/IWapPushManager;
+Lcom/android/internal/telephony/LastCallFailCause;
+Lcom/android/internal/telephony/LinkCapacityEstimate;
+Lcom/android/internal/telephony/metrics/-$$Lambda$TelephonyMetrics$fLmZDbNadlr6LF7zSJ6jCR1AAsk;
+Lcom/android/internal/telephony/metrics/-$$Lambda$TelephonyMetrics$x2dJi76S2YQdpSTfY8RZ8qC_K6g;
+Lcom/android/internal/telephony/metrics/ModemPowerMetrics;
+Lcom/android/internal/telephony/metrics/TelephonyMetrics$1;
+Lcom/android/internal/telephony/MultiSimSettingController$1;
+[Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierAttribute;
+[Lcom/android/internal/telephony/nano/CarrierIdProto$CarrierId;
+[Lcom/android/internal/telephony/nano/TelephonyProto$ActiveSubscriptionInfo;
+Lcom/android/internal/telephony/nano/TelephonyProto$EmergencyNumberInfo;
+Lcom/android/internal/telephony/nano/TelephonyProto$ModemPowerStats;
+[Lcom/android/internal/telephony/nano/TelephonyProto$RilDataCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession;
+Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession;
+[Lcom/android/internal/telephony/nano/TelephonyProto$SmsSession$Event;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyCallSession$Event$RilCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent$OnDemandDataSwitch;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyEvent$RilDeactivateDataCall;
+[Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyHistogram;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyHistogram;
+Lcom/android/internal/telephony/nano/TelephonyProto$TelephonyLog;
+Lcom/android/internal/telephony/nano/TelephonyProto$Time;
+Lcom/android/internal/telephony/NitzStateMachineImpl;
+Lcom/android/internal/telephony/OperatorInfo$State;
+[Lcom/android/internal/telephony/Phone;
+Lcom/android/internal/telephony/PhoneInternalInterface$DialArgs;
+Lcom/android/internal/telephony/PhoneInternalInterface$DialArgs$Builder;
+Lcom/android/internal/telephony/PhoneInternalInterface$SuppService;
+Lcom/android/internal/telephony/PhoneSwitcher$2;
+Lcom/android/internal/telephony/PhoneSwitcher$DefaultNetworkCallback;
+Lcom/android/internal/telephony/PhoneSwitcher$EmergencyOverrideRequest;
+[Lcom/android/internal/telephony/PhoneSwitcher$PhoneState;
+Lcom/android/internal/telephony/protobuf/nano/CodedOutputByteBufferNano$OutOfSpaceException;
+Lcom/android/internal/telephony/protobuf/nano/MessageNanoPrinter;
+Lcom/android/internal/telephony/RadioBugDetector;
+[Lcom/android/internal/telephony/RIL;
+Lcom/android/internal/telephony/sip/SipPhone;
+Lcom/android/internal/telephony/SmsBroadcastUndelivered$SmsReferenceKey;
+Lcom/android/internal/telephony/SmsConstants$MessageClass;
+Lcom/android/internal/telephony/SmsController;
+Lcom/android/internal/telephony/SMSDispatcher$DataSmsSender;
+Lcom/android/internal/telephony/SMSDispatcher$SmsSenderCallback;
+[Lcom/android/internal/telephony/SMSDispatcher$SmsTracker;
+Lcom/android/internal/telephony/SMSDispatcher$SmsTracker;
+Lcom/android/internal/telephony/SMSDispatcher$TextSmsSender;
+Lcom/android/internal/telephony/SmsNumberUtils;
+Lcom/android/internal/telephony/SmsPermissions;
+Lcom/android/internal/telephony/SmsResponse;
+Lcom/android/internal/telephony/TimeServiceHelper;
+Lcom/android/internal/telephony/uicc/CsimFileHandler;
+[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus;
+Lcom/android/internal/telephony/uicc/IccException;
+Lcom/android/internal/telephony/uicc/IccFileNotFound;
+Lcom/android/internal/telephony/uicc/IccFileTypeMismatch;
+Lcom/android/internal/telephony/uicc/IccRefreshResponse;
+Lcom/android/internal/telephony/uicc/IccVmNotSupportedException;
+Lcom/android/internal/telephony/uicc/InstallCarrierAppTrampolineActivity;
+Lcom/android/internal/telephony/uicc/IsimFileHandler;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimDomainLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimImpiLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimImpuLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimIstLoaded;
+Lcom/android/internal/telephony/uicc/IsimUiccRecords$EfIsimPcscfLoaded;
+[Lcom/android/internal/telephony/uicc/PlmnActRecord;
+Lcom/android/internal/telephony/uicc/RuimFileHandler;
+Lcom/android/internal/telephony/uicc/RuimRecords;
+Lcom/android/internal/telephony/uicc/SIMFileHandler;
+[Lcom/android/internal/telephony/uicc/UiccCardApplication;
+Lcom/android/internal/telephony/uicc/UiccCarrierPrivilegeRules$TLV;
+Lcom/android/internal/telephony/uicc/UiccPkcs15$FileHandler;
+Lcom/android/internal/telephony/uicc/UiccProfile$5;
+[Lcom/android/internal/telephony/uicc/UiccSlot;
+Lcom/android/internal/telephony/util/SMSDispatcherUtil;
+Lcom/android/internal/telephony/UUSInfo;
+Lcom/android/internal/telephony/VisualVoicemailSmsFilter;
+Lcom/android/internal/textservice/ISpellCheckerService;
+Lcom/android/internal/textservice/ISpellCheckerService$Stub;
+Lcom/android/internal/textservice/ISpellCheckerService$Stub$Proxy;
+Lcom/android/internal/textservice/ISpellCheckerSession$Stub;
+Lcom/android/internal/textservice/ISpellCheckerSessionListener$Stub$Proxy;
+Lcom/android/internal/textservice/ITextServicesSessionListener$Stub$Proxy;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$D1OlZP6xIpu72ypnJd0fzx0wd6I;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$vCLO_0ezRxkpSERUWCFrJ0ph5jg;
+Lcom/android/internal/util/-$$Lambda$DumpUtils$X8irOs5hfloCKy89_l1HRA1QeG0;
+Lcom/android/internal/util/-$$Lambda$eRa1rlfDk6Og2yFeXGHqUGPzRF0;
+Lcom/android/internal/util/-$$Lambda$FunctionalUtils$koCSI8D7Nu5vOJTVTEj0m3leo_U;
+Lcom/android/internal/util/-$$Lambda$grRTg3idX3yJe9Zyx-tmLBiD1DM;
+Lcom/android/internal/util/-$$Lambda$JwOUSWW2-Jzu15y4Kn4JuPh8tWM;
+Lcom/android/internal/util/-$$Lambda$kVylv1rl9MOSbHFZoVyK5dl1kfY;
+Lcom/android/internal/util/-$$Lambda$TCbPpgWlKJUHZgFKCczglAvxLfw;
+Lcom/android/internal/util/ContrastColorUtil;
+Lcom/android/internal/util/ContrastColorUtil$ColorUtilsFromCompat;
+Lcom/android/internal/util/function/HeptPredicate;
+Lcom/android/internal/util/function/HexPredicate;
+Lcom/android/internal/util/function/NonaPredicate;
+Lcom/android/internal/util/function/OctPredicate;
+Lcom/android/internal/util/function/QuadPredicate;
+Lcom/android/internal/util/function/QuintPredicate;
+Lcom/android/internal/util/MessageUtils$DuplicateConstantError;
+Lcom/android/internal/util/ProcFileReader;
+[Lcom/android/internal/util/StateMachine$SmHandler$StateInfo;
+Lcom/android/internal/util/SyncResultReceiver$TimeoutException;
+Lcom/android/internal/util/UserIcons;
+Lcom/android/internal/view/IInputMethod$Stub$Proxy;
+Lcom/android/internal/view/IInputMethodClient$Stub$Proxy;
+Lcom/android/internal/view/IInputSessionCallback$Stub$Proxy;
+Lcom/android/internal/view/InputConnectionWrapper;
+Lcom/android/internal/view/InputConnectionWrapper$InputContextCallback;
+Lcom/android/internal/view/menu/ActionMenuItemView;
+Lcom/android/internal/view/menu/ActionMenuItemView$ActionMenuItemForwardingListener;
+Lcom/android/internal/view/menu/MenuPopupHelper$1;
+Lcom/android/internal/view/menu/MenuView$ItemView;
+Lcom/android/internal/view/OneShotPreDrawListener;
+Lcom/android/internal/view/RotationPolicy$1;
+Lcom/android/internal/widget/ActionBarOverlayLayout;
+Lcom/android/internal/widget/DecorCaptionView;
+Lcom/android/internal/widget/ICheckCredentialProgressCallback$Stub;
+Lcom/android/internal/widget/ICheckCredentialProgressCallback$Stub$Proxy;
+Lcom/android/internal/widget/SwipeDismissLayout;
+Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory;
+Lcom/android/okhttp/internalandroidapi/HttpURLConnectionFactory$DnsAdapter;
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;
+Lcom/android/org/conscrypt/TrustedCertificateStore;
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;
+Lcom/android/org/conscrypt/TrustManagerImpl;
+[Lcom/android/ph
+[Lcom/android/phone/ecc/nano/ProtobufEccData$CountryInfo;
+[Lcom/android/phone/ecc/nano/ProtobufEccData$EccInfo;
+Lcom/android/server/backup/AccountManagerBackupHelper;
+Lcom/android/server/backup/AccountSyncSettingsBackupHelper;
+Lcom/android/server/backup/NotificationBackupHelper;
+Lcom/android/server/backup/PermissionBackupHelper;
+Lcom/android/server/backup/PreferredActivityBackupHelper;
+Lcom/android/server/backup/ShortcutBackupHelper;
+Lcom/android/server/backup/SliceBackupHelper;
+Lcom/android/server/backup/UsageStatsBackupHelper;
+Lcom/android/server/BootReceiver;
+Lcom/android/server/BootReceiver$1;
+Lcom/android/server/BootReceiver$2;
+[Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$IpConnectivityEvent;
+[Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$Pair;
+Lcom/android/server/connectivity/metrics/nano/IpConnectivityLogClass$Pair;
+Lcom/android/server/sip/SipService$ConnectivityReceiver;
+Lcom/android/server/sip/SipService$MyExecutor;
+Lcom/android/server/sip/SipWakeLock;
+Lcom/android/server/sip/SipWakeupTimer$MyEventComparator;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$AlertReasonCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$ConnectionEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$ConnectToNetworkNotificationAndActionCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$DeviceMobilityStatePnoScanStats;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$GroupEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$HistogramBucketInt32;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$Int32Count;
+Lcom/android/server/wifi/nano/WifiMetricsProto$Int32Count;
+Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$ExperimentProbeCounts;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$LinkProbeFailureReasonCount;
+Lcom/android/server/wifi/nano/WifiMetricsProto$LinkProbeStats$LinkProbeFailureReasonCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$LinkSpeedCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$NetworkSelectionExperimentDecisions;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$NumConnectableNetworksBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$P2pConnectionEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$PasspointProfileTypeCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$RssiPollCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApConnectedClientsEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApDurationBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$SoftApReturnCodeCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$StaEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$HistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$HistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$NanStatusHistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiAwareLog$NanStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiConfigStoreIO$DurationBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiDppLog$DppConfiguratorSuccessStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiDppLog$DppFailureStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiIsUnusableEvent;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiLog$ScanReturnEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiLog$WifiSystemStateEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$HistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$HistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttIndividualStatusHistogramBucket;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttIndividualStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiRttLog$RttOverallStatusHistogramBucket;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiScoreCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityScoreCount;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStats;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStatsEntry;
+Lcom/android/server/wifi/nano/WifiMetricsProto$WifiUsabilityStatsEntry;
+[Lcom/android/server/wifi/nano/WifiMetricsProto$WifiWakeStats$Session;
+Lcom/android/server/wm/nano/WindowManagerProtos$TaskSnapshotProto;
+Lcom/google/android/rappor/Encoder;
+Lcom/google/android/rappor/HmacDrbg;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel$Conversation;
+[Lcom/google/android/textclassifier/ActionsSuggestionsModel$ConversationMessage;
+Lcom/google/android/textclassifier/ActionsSuggestionsModel$ConversationMessage;
+Lcom/google/android/textclassifier/AnnotatorModel;
+Lcom/google/android/textclassifier/LangIdModel;
+[Ljava/lang/Double;
+[Ljava/lang/Runnable;
+[Ljava/lang/Void;
+[Ljava/net/InetAddress;
+Ljava/nio/file/Files$AcceptAllFilter;
+[Ljava/nio/file/OpenOption;
+[[Ljava/security/cert/Certificate;
+[Ljava/security/cert/Certificate;
+[[Ljava/security/cert/X509Certificate;
+[Ljava/security/cert/X509Certificate;
+[Ljava/security/MessageDigest;
+Ljava/security/spec/PSSParameterSpec;
+Ljava/time/-$$Lambda$up1HpCqucM_DXyY-rpDOyCcdmIA;
+[Ljava/util/concurrent/RunnableScheduledFuture;
+[Ljava/util/HashMap;
+Ljava/util/logging/Logger$SystemLoggerHelper;
+Ljava/util/PropertyPermission;
+Ljava/util/stream/-$$Lambda$MatchOps$emK14UX33I4-nqH2o5l7hLEVAy8;
+[Ljavax/net/ssl/TrustManager;
+Llibcore/icu/RelativeDateTimeFormatter;
+Lsun/security/util/SecurityConstants;
+Lsun/util/locale/LocaleMatcher;
diff --git a/config/dirty-image-objects b/config/dirty-image-objects
index ec2568d..2dfe019 100644
--- a/config/dirty-image-objects
+++ b/config/dirty-image-objects
@@ -255,7 +255,7 @@
 com.android.internal.policy.DecorView
 com.android.internal.statusbar.IStatusBarService
 com.android.internal.telephony.AppSmsManager
-com.android.internal.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
+android.telephony.CallerInfoAsyncQuery$OnQueryCompleteListener
 com.android.internal.telephony.CarrierActionAgent
 com.android.internal.telephony.cat.CatService
 com.android.internal.telephony.cat.IconLoader
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 9f48f8a..a6e1f0a 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -591,30 +591,6 @@
 Landroid/telephony/mbms/IMbmsStreamingSessionCallback$Stub;-><init>()V
 Landroid/telephony/mbms/IStreamingServiceCallback$Stub;-><init>()V
 Landroid/telephony/mbms/vendor/IMbmsStreamingService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/telephony/mbms/vendor/IMbmsStreamingService;
-Landroid/telephony/SmsCbCmasInfo;->getCategory()I
-Landroid/telephony/SmsCbCmasInfo;->getCertainty()I
-Landroid/telephony/SmsCbCmasInfo;->getMessageClass()I
-Landroid/telephony/SmsCbCmasInfo;->getResponseType()I
-Landroid/telephony/SmsCbCmasInfo;->getSeverity()I
-Landroid/telephony/SmsCbCmasInfo;->getUrgency()I
-Landroid/telephony/SmsCbEtwsInfo;->getWarningType()I
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;)V
-Landroid/telephony/SmsCbLocation;-><init>(Ljava/lang/String;II)V
-Landroid/telephony/SmsCbLocation;->getCid()I
-Landroid/telephony/SmsCbLocation;->getLac()I
-Landroid/telephony/SmsCbLocation;->getPlmn()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;-><init>(Landroid/os/Parcel;)V
-Landroid/telephony/SmsCbMessage;->getCmasWarningInfo()Landroid/telephony/SmsCbCmasInfo;
-Landroid/telephony/SmsCbMessage;->getEtwsWarningInfo()Landroid/telephony/SmsCbEtwsInfo;
-Landroid/telephony/SmsCbMessage;->getGeographicalScope()I
-Landroid/telephony/SmsCbMessage;->getLanguageCode()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getLocation()Landroid/telephony/SmsCbLocation;
-Landroid/telephony/SmsCbMessage;->getMessageBody()Ljava/lang/String;
-Landroid/telephony/SmsCbMessage;->getMessageFormat()I
-Landroid/telephony/SmsCbMessage;->getSerialNumber()I
-Landroid/telephony/SmsCbMessage;->getServiceCategory()I
-Landroid/telephony/SmsCbMessage;->isCmasMessage()Z
-Landroid/telephony/SmsCbMessage;->isEmergencyMessage()Z
 Landroid/telephony/TelephonyManager$MultiSimVariants;->values()[Landroid/telephony/TelephonyManager$MultiSimVariants;
 Landroid/util/Singleton;-><init>()V
 Landroid/view/accessibility/IAccessibilityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -1174,8 +1150,6 @@
 Lcom/android/internal/statusbar/IStatusBarService$Stub;-><init>()V
 Lcom/android/internal/statusbar/IStatusBarService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/statusbar/IStatusBarService;
 Lcom/android/internal/telecom/ITelecomService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telecom/ITelecomService;
-Lcom/android/internal/telephony/IIccPhoneBook$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Lcom/android/internal/telephony/IIccPhoneBook$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IIccPhoneBook;
 Lcom/android/internal/telephony/IMms$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IMms;
 Lcom/android/internal/telephony/IPhoneStateListener$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/IPhoneStateListener;
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
@@ -1183,7 +1157,6 @@
 Lcom/android/internal/telephony/IPhoneSubInfo$Stub;->TRANSACTION_getDeviceId:I
 Lcom/android/internal/telephony/ISms$Stub;-><init>()V
 Lcom/android/internal/telephony/ISms$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ISms;
-Lcom/android/internal/telephony/ISub$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Lcom/android/internal/telephony/ISub$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ISub;
 Lcom/android/internal/telephony/ITelephony$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Lcom/android/internal/telephony/ITelephony$Stub$Proxy;->mRemote:Landroid/os/IBinder;
@@ -1207,5 +1180,283 @@
 Lcom/android/server/ResettableTimeout$T;-><init>(Lcom/android/server/ResettableTimeout;)V
 Lcom/google/android/gles_jni/EGLImpl;-><init>()V
 Lcom/google/android/gles_jni/GLImpl;-><init>()V
-Lcom/google/android/mms/pdu/PduParser;->$assertionsDisabled:Z
+Lcom/google/android/mms/ContentType;->getAudioTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getImageTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->getVideoTypes()Ljava/util/ArrayList;
+Lcom/google/android/mms/ContentType;->isAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isDrmType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedAudioType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedImageType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isSupportedVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isTextType(Ljava/lang/String;)Z
+Lcom/google/android/mms/ContentType;->isVideoType(Ljava/lang/String;)Z
+Lcom/google/android/mms/InvalidHeaderValueException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>()V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V
+Lcom/google/android/mms/MmsException;-><init>(Ljava/lang/Throwable;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(I[B)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/AcknowledgeInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/Base64;->decodeBase64([B)[B
+Lcom/google/android/mms/pdu/CharacterSets;->getMibEnumValue(Ljava/lang/String;)I
+Lcom/google/android/mms/pdu/CharacterSets;->getMimeName(I)Ljava/lang/String;
+Lcom/google/android/mms/pdu/DeliveryInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/DeliveryInd;->getDate()J
+Lcom/google/android/mms/pdu/DeliveryInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/DeliveryInd;->getStatus()I
+Lcom/google/android/mms/pdu/DeliveryInd;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(I[B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/EncodedStringValue;-><init>([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->appendTextString([B)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->concat([Lcom/google/android/mms/pdu/EncodedStringValue;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->copy(Lcom/google/android/mms/pdu/EncodedStringValue;)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->encodeStrings([Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->extract(Ljava/lang/String;)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getCharacterSet()I
+Lcom/google/android/mms/pdu/EncodedStringValue;->getString()Ljava/lang/String;
+Lcom/google/android/mms/pdu/EncodedStringValue;->getTextString()[B
+Lcom/google/android/mms/pdu/EncodedStringValue;->setCharacterSet(I)V
+Lcom/google/android/mms/pdu/EncodedStringValue;->setTextString([B)V
+Lcom/google/android/mms/pdu/GenericPdu;-><init>()V
+Lcom/google/android/mms/pdu/GenericPdu;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/GenericPdu;->getMessageType()I
+Lcom/google/android/mms/pdu/GenericPdu;->getPduHeaders()Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->mPduHeaders:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/GenericPdu;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/GenericPdu;->setMessageType(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>()V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->addTo(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getBody()Lcom/google/android/mms/pdu/PduBody;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getDate()J
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getPriority()I
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->getTo()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setBody(Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setDate(J)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setPriority(I)V
+Lcom/google/android/mms/pdu/MultimediaMessagePdu;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>()V
+Lcom/google/android/mms/pdu/NotificationInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotificationInd;->getContentClass()I
+Lcom/google/android/mms/pdu/NotificationInd;->getContentLocation()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/NotificationInd;->getExpiry()J
+Lcom/google/android/mms/pdu/NotificationInd;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageClass()[B
+Lcom/google/android/mms/pdu/NotificationInd;->getMessageSize()J
+Lcom/google/android/mms/pdu/NotificationInd;->getSubject()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/NotificationInd;->getTransactionId()[B
+Lcom/google/android/mms/pdu/NotificationInd;->setContentClass(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/NotificationInd;->setExpiry(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/NotificationInd;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/NotificationInd;->setSubject(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/NotificationInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(I[BI)V
+Lcom/google/android/mms/pdu/NotifyRespInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setReportAllowed(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setStatus(I)V
+Lcom/google/android/mms/pdu/NotifyRespInd;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/PduBody;-><init>()V
+Lcom/google/android/mms/pdu/PduBody;->addPart(ILcom/google/android/mms/pdu/PduPart;)V
+Lcom/google/android/mms/pdu/PduBody;->addPart(Lcom/google/android/mms/pdu/PduPart;)Z
+Lcom/google/android/mms/pdu/PduBody;->getPart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentId(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByContentLocation(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByFileName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartByName(Ljava/lang/String;)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduBody;->getPartIndex(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduBody;->getPartsNum()I
+Lcom/google/android/mms/pdu/PduBody;->removePart(I)Lcom/google/android/mms/pdu/PduPart;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->copy()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->mark()Lcom/google/android/mms/pdu/PduComposer$PositionMarker;
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->newbuf()V
+Lcom/google/android/mms/pdu/PduComposer$BufferStack;->pop()V
+Lcom/google/android/mms/pdu/PduComposer$PositionMarker;->getLength()I
+Lcom/google/android/mms/pdu/PduComposer;-><init>(Landroid/content/Context;Lcom/google/android/mms/pdu/GenericPdu;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendEncodedString(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendHeader(I)I
+Lcom/google/android/mms/pdu/PduComposer;->appendLongInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendOctet(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendQuotedString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendShortInteger(I)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduComposer;->appendTextString([B)V
+Lcom/google/android/mms/pdu/PduComposer;->appendUintvarInteger(J)V
+Lcom/google/android/mms/pdu/PduComposer;->appendValueLength(J)V
+Lcom/google/android/mms/pdu/PduComposer;->arraycopy([BII)V
+Lcom/google/android/mms/pdu/PduComposer;->make()[B
+Lcom/google/android/mms/pdu/PduComposer;->mContentTypeMap:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduComposer;->mMessage:Ljava/io/ByteArrayOutputStream;
+Lcom/google/android/mms/pdu/PduComposer;->mPdu:Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduComposer;->mPduHeader:Lcom/google/android/mms/pdu/PduHeaders;
+Lcom/google/android/mms/pdu/PduComposer;->mPosition:I
+Lcom/google/android/mms/pdu/PduComposer;->mResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduComposer;->mStack:Lcom/google/android/mms/pdu/PduComposer$BufferStack;
+Lcom/google/android/mms/pdu/PduContentTypes;->contentTypes:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduHeaders;-><init>()V
+Lcom/google/android/mms/pdu/PduHeaders;->appendEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValue(I)Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getEncodedStringValues(I)[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/PduHeaders;->getLongInteger(I)J
+Lcom/google/android/mms/pdu/PduHeaders;->getOctet(I)I
+Lcom/google/android/mms/pdu/PduHeaders;->getTextString(I)[B
+Lcom/google/android/mms/pdu/PduHeaders;->setEncodedStringValue(Lcom/google/android/mms/pdu/EncodedStringValue;I)V
+Lcom/google/android/mms/pdu/PduHeaders;->setLongInteger(JI)V
+Lcom/google/android/mms/pdu/PduHeaders;->setOctet(II)V
+Lcom/google/android/mms/pdu/PduParser;-><init>([BZ)V
+Lcom/google/android/mms/pdu/PduParser;->checkPartPosition(Lcom/google/android/mms/pdu/PduPart;)I
+Lcom/google/android/mms/pdu/PduParser;->log(Ljava/lang/String;)V
+Lcom/google/android/mms/pdu/PduParser;->parse()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduParser;->parseContentType(Ljava/io/ByteArrayInputStream;Ljava/util/HashMap;)[B
+Lcom/google/android/mms/pdu/PduParser;->parsePartHeaders(Ljava/io/ByteArrayInputStream;Lcom/google/android/mms/pdu/PduPart;I)Z
+Lcom/google/android/mms/pdu/PduParser;->parseShortInteger(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseUnsignedInt(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseValueLength(Ljava/io/ByteArrayInputStream;)I
+Lcom/google/android/mms/pdu/PduParser;->parseWapString(Ljava/io/ByteArrayInputStream;I)[B
+Lcom/google/android/mms/pdu/PduPart;-><init>()V
+Lcom/google/android/mms/pdu/PduPart;->generateLocation()Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPart;->getCharset()I
+Lcom/google/android/mms/pdu/PduPart;->getContentDisposition()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentId()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentLocation()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentTransferEncoding()[B
+Lcom/google/android/mms/pdu/PduPart;->getContentType()[B
+Lcom/google/android/mms/pdu/PduPart;->getData()[B
+Lcom/google/android/mms/pdu/PduPart;->getDataLength()I
+Lcom/google/android/mms/pdu/PduPart;->getDataUri()Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPart;->getFilename()[B
+Lcom/google/android/mms/pdu/PduPart;->getName()[B
+Lcom/google/android/mms/pdu/PduPart;->setCharset(I)V
+Lcom/google/android/mms/pdu/PduPart;->setContentDisposition([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentId([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentLocation([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentTransferEncoding([B)V
+Lcom/google/android/mms/pdu/PduPart;->setContentType([B)V
+Lcom/google/android/mms/pdu/PduPart;->setData([B)V
+Lcom/google/android/mms/pdu/PduPart;->setDataUri(Landroid/net/Uri;)V
+Lcom/google/android/mms/pdu/PduPart;->setFilename([B)V
+Lcom/google/android/mms/pdu/PduPart;->setName([B)V
+Lcom/google/android/mms/pdu/PduPersister;->ADDRESS_FIELDS:[I
+Lcom/google/android/mms/pdu/PduPersister;->CHARSET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->ENCODED_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->getByteArrayFromPartColumn(Landroid/database/Cursor;I)[B
+Lcom/google/android/mms/pdu/PduPersister;->getBytes(Ljava/lang/String;)[B
+Lcom/google/android/mms/pdu/PduPersister;->getIntegerFromPartColumn(Landroid/database/Cursor;I)Ljava/lang/Integer;
+Lcom/google/android/mms/pdu/PduPersister;->getPartContentType(Lcom/google/android/mms/pdu/PduPart;)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->getPduPersister(Landroid/content/Context;)Lcom/google/android/mms/pdu/PduPersister;
+Lcom/google/android/mms/pdu/PduPersister;->getPendingMessages(J)Landroid/database/Cursor;
+Lcom/google/android/mms/pdu/PduPersister;->load(Landroid/net/Uri;)Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/pdu/PduPersister;->loadRecipients(ILjava/util/HashSet;Ljava/util/HashMap;Z)V
+Lcom/google/android/mms/pdu/PduPersister;->LONG_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->mContentResolver:Landroid/content/ContentResolver;
+Lcom/google/android/mms/pdu/PduPersister;->mContext:Landroid/content/Context;
+Lcom/google/android/mms/pdu/PduPersister;->MESSAGE_BOX_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->move(Landroid/net/Uri;Landroid/net/Uri;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->mTelephonyManager:Landroid/telephony/TelephonyManager;
+Lcom/google/android/mms/pdu/PduPersister;->OCTET_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->PART_PROJECTION:[Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->PDU_CACHE_INSTANCE:Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/pdu/PduPersister;->persist(Lcom/google/android/mms/pdu/GenericPdu;Landroid/net/Uri;ZZLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->persistAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->persistPart(Lcom/google/android/mms/pdu/PduPart;JLjava/util/HashMap;)Landroid/net/Uri;
+Lcom/google/android/mms/pdu/PduPersister;->TEXT_STRING_COLUMN_NAME_MAP:Ljava/util/HashMap;
+Lcom/google/android/mms/pdu/PduPersister;->toIsoString([B)Ljava/lang/String;
+Lcom/google/android/mms/pdu/PduPersister;->updateAddress(JI[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateHeaders(Landroid/net/Uri;Lcom/google/android/mms/pdu/SendReq;)V
+Lcom/google/android/mms/pdu/PduPersister;->updateParts(Landroid/net/Uri;Lcom/google/android/mms/pdu/PduBody;Ljava/util/HashMap;)V
+Lcom/google/android/mms/pdu/QuotedPrintable;->decodeQuotedPrintable([B)[B
+Lcom/google/android/mms/pdu/ReadOrigInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadOrigInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadOrigInd;->getReadStatus()I
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/EncodedStringValue;[BII[Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/ReadRecInd;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/ReadRecInd;->getMessageId()[B
+Lcom/google/android/mms/pdu/ReadRecInd;->setDate(J)V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>()V
+Lcom/google/android/mms/pdu/RetrieveConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getContentType()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getFrom()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageClass()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->getReadReport()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveStatus()I
+Lcom/google/android/mms/pdu/RetrieveConf;->getRetrieveText()Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/RetrieveConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/RetrieveConf;->setContentType([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setFrom(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setMessageId([B)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setReadReport(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveStatus(I)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setRetrieveText(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/RetrieveConf;->setTransactionId([B)V
+Lcom/google/android/mms/pdu/SendConf;-><init>()V
+Lcom/google/android/mms/pdu/SendConf;-><init>(Lcom/google/android/mms/pdu/PduHeaders;)V
+Lcom/google/android/mms/pdu/SendConf;->getMessageId()[B
+Lcom/google/android/mms/pdu/SendConf;->getResponseStatus()I
+Lcom/google/android/mms/pdu/SendConf;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;-><init>()V
+Lcom/google/android/mms/pdu/SendReq;-><init>(Lcom/google/android/mms/pdu/PduHeaders;Lcom/google/android/mms/pdu/PduBody;)V
+Lcom/google/android/mms/pdu/SendReq;->addBcc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->addCc(Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->getBcc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getCc()[Lcom/google/android/mms/pdu/EncodedStringValue;
+Lcom/google/android/mms/pdu/SendReq;->getContentType()[B
+Lcom/google/android/mms/pdu/SendReq;->getDeliveryReport()I
+Lcom/google/android/mms/pdu/SendReq;->getExpiry()J
+Lcom/google/android/mms/pdu/SendReq;->getMessageClass()[B
+Lcom/google/android/mms/pdu/SendReq;->getMessageSize()J
+Lcom/google/android/mms/pdu/SendReq;->getReadReport()I
+Lcom/google/android/mms/pdu/SendReq;->getTransactionId()[B
+Lcom/google/android/mms/pdu/SendReq;->setBcc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setCc([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setContentType([B)V
+Lcom/google/android/mms/pdu/SendReq;->setDeliveryReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setExpiry(J)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageClass([B)V
+Lcom/google/android/mms/pdu/SendReq;->setMessageSize(J)V
+Lcom/google/android/mms/pdu/SendReq;->setReadReport(I)V
+Lcom/google/android/mms/pdu/SendReq;->setTo([Lcom/google/android/mms/pdu/EncodedStringValue;)V
+Lcom/google/android/mms/pdu/SendReq;->setTransactionId([B)V
+Lcom/google/android/mms/util/AbstractCache;-><init>()V
+Lcom/google/android/mms/util/AbstractCache;->get(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/AbstractCache;->purgeAll()V
+Lcom/google/android/mms/util/AbstractCache;->put(Ljava/lang/Object;Ljava/lang/Object;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->isDrmConvertNeeded(Ljava/lang/String;)Z
+Lcom/google/android/mms/util/DownloadDrmHelper;->modifyDrmFwLockFileExtension(Ljava/lang/String;)Ljava/lang/String;
+Lcom/google/android/mms/util/DrmConvertSession;->close(Ljava/lang/String;)I
+Lcom/google/android/mms/util/DrmConvertSession;->convert([BI)[B
+Lcom/google/android/mms/util/DrmConvertSession;->open(Landroid/content/Context;Ljava/lang/String;)Lcom/google/android/mms/util/DrmConvertSession;
+Lcom/google/android/mms/util/PduCache;-><init>()V
+Lcom/google/android/mms/util/PduCache;->getInstance()Lcom/google/android/mms/util/PduCache;
+Lcom/google/android/mms/util/PduCache;->isUpdating(Landroid/net/Uri;)Z
+Lcom/google/android/mms/util/PduCache;->purge(Landroid/net/Uri;)Lcom/google/android/mms/util/PduCacheEntry;
+Lcom/google/android/mms/util/PduCache;->purge(Ljava/lang/Object;)Ljava/lang/Object;
+Lcom/google/android/mms/util/PduCache;->purgeAll()V
+Lcom/google/android/mms/util/PduCacheEntry;-><init>(Lcom/google/android/mms/pdu/GenericPdu;IJ)V
+Lcom/google/android/mms/util/PduCacheEntry;->getMessageBox()I
+Lcom/google/android/mms/util/PduCacheEntry;->getPdu()Lcom/google/android/mms/pdu/GenericPdu;
+Lcom/google/android/mms/util/PduCacheEntry;->getThreadId()J
+Lcom/google/android/mms/util/SqliteWrapper;->checkSQLiteException(Landroid/content/Context;Landroid/database/sqlite/SQLiteException;)V
+Lcom/google/android/mms/util/SqliteWrapper;->delete(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I
+Lcom/google/android/mms/util/SqliteWrapper;->insert(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri;
+Lcom/google/android/mms/util/SqliteWrapper;->query(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
+Lcom/google/android/mms/util/SqliteWrapper;->requery(Landroid/content/Context;Landroid/database/Cursor;)Z
+Lcom/google/android/mms/util/SqliteWrapper;->update(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I
 Lcom/google/android/util/AbstractMessageParser$Token$Type;->values()[Lcom/google/android/util/AbstractMessageParser$Token$Type;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 778a4d7..e117e68 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
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/AccessibilityGestureInfo.aidl b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
similarity index 94%
rename from core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
rename to core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
index 2539051..58a9b64 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.aidl
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.aidl
@@ -16,4 +16,4 @@
 
 package android.accessibilityservice;
 
-parcelable AccessibilityGestureInfo;
+parcelable AccessibilityGestureEvent;
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
similarity index 86%
rename from core/java/android/accessibilityservice/AccessibilityGestureInfo.java
rename to core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 28c1dea..b540175 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -44,17 +44,17 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * This class describes the gesture information including gesture id and which display it happens
+ * 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(AccessibilityGestureInfo)
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
  */
 
-public final class AccessibilityGestureInfo implements Parcelable {
+public final class AccessibilityGestureEvent implements Parcelable {
 
     /** @hide */
     @IntDef(prefix = { "GESTURE_" }, value = {
@@ -84,12 +84,12 @@
 
     /** @hide */
     @TestApi
-    public AccessibilityGestureInfo(int gestureId, int displayId) {
+    public AccessibilityGestureEvent(int gestureId, int displayId) {
         mGestureId = gestureId;
         mDisplayId = displayId;
     }
 
-    private AccessibilityGestureInfo(@NonNull Parcel parcel) {
+    private AccessibilityGestureEvent(@NonNull Parcel parcel) {
         mGestureId = parcel.readInt();
         mDisplayId = parcel.readInt();
     }
@@ -117,7 +117,7 @@
     @NonNull
     @Override
     public String toString() {
-        StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureInfo[");
+        StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
         stringBuilder.append("gestureId: ").append(mGestureId);
         stringBuilder.append(", ");
         stringBuilder.append("displayId: ").append(mDisplayId);
@@ -142,14 +142,14 @@
     /**
      * @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 static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+            new Parcelable.Creator<AccessibilityGestureEvent>() {
+        public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+            return new AccessibilityGestureEvent(parcel);
         }
 
-        public AccessibilityGestureInfo[] newArray(int size) {
-            return new AccessibilityGestureInfo[size];
+        public AccessibilityGestureEvent[] newArray(int size) {
+            return new AccessibilityGestureEvent[size];
         }
     };
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a8daf91..d3c274f 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -382,7 +382,7 @@
         void onServiceConnected();
         void init(int connectionId, IBinder windowToken);
         /** The detected gesture information for different displays */
-        boolean onGesture(AccessibilityGestureInfo gestureInfo);
+        boolean onGesture(AccessibilityGestureEvent gestureInfo);
         boolean onKeyEvent(KeyEvent event);
         /** Magnification changed callbacks for different displays */
         void onMagnificationChanged(int displayId, @NonNull Region region,
@@ -517,7 +517,7 @@
     }
 
     /**
-     * Called by {@link #onGesture(AccessibilityGestureInfo)} when the user performs a specific
+     * Called by {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
      * gesture on the default display.
      *
      * <strong>Note:</strong> To receive gestures an accessibility service must
@@ -528,7 +528,7 @@
      * @param gestureId The unique id of the performed gesture.
      *
      * @return Whether the gesture was handled.
-     * @deprecated Override {@link #onGesture(AccessibilityGestureInfo)} instead.
+     * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
      *
      * @see #GESTURE_SWIPE_UP
      * @see #GESTURE_SWIPE_UP_AND_LEFT
@@ -564,14 +564,14 @@
      * <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
      * touch screen is default display.
      *
-     * @param gestureInfo The information of gesture.
+     * @param gestureEvent The information of gesture.
      *
      * @return Whether the gesture was handled.
      *
      */
-    public boolean onGesture(@NonNull AccessibilityGestureInfo gestureInfo) {
-        if (gestureInfo.getDisplayId() == Display.DEFAULT_DISPLAY) {
-            onGesture(gestureInfo.getGestureId());
+    public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+        if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            onGesture(gestureEvent.getGestureId());
         }
         return false;
     }
@@ -1725,8 +1725,8 @@
             }
 
             @Override
-            public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
-                return AccessibilityService.this.onGesture(gestureInfo);
+            public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+                return AccessibilityService.this.onGesture(gestureEvent);
             }
 
             @Override
@@ -1826,7 +1826,7 @@
         }
 
         @Override
-        public void onGesture(AccessibilityGestureInfo gestureInfo) {
+        public void onGesture(AccessibilityGestureEvent gestureInfo) {
             Message message = mCaller.obtainMessageO(DO_ON_GESTURE, gestureInfo);
             mCaller.sendMessage(message);
         }
@@ -1942,7 +1942,7 @@
 
                 case DO_ON_GESTURE: {
                     if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
-                        mCallback.onGesture((AccessibilityGestureInfo) message.obj);
+                        mCallback.onGesture((AccessibilityGestureEvent) message.obj);
                     }
                 } return;
 
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index e0d5e44..8ec3aea 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -20,7 +20,7 @@
 import android.graphics.Region;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.view.KeyEvent;
 
 /**
@@ -36,7 +36,7 @@
 
     void onInterrupt();
 
-    void onGesture(in AccessibilityGestureInfo gestureInfo);
+    void onGesture(in AccessibilityGestureEvent gestureEvent);
 
     void clearAccessibilityCache();
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 15c6425..bf7d632 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -146,6 +146,7 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -945,6 +946,10 @@
     /** @hide */
     boolean mEnterAnimationComplete;
 
+    /** Track last dispatched multi-window and PiP mode to client, internal debug purpose **/
+    private Boolean mLastDispatchedIsInMultiWindowMode;
+    private Boolean mLastDispatchedIsInPictureInPictureMode;
+
     private static native String getDlWarning();
 
     /** Return the intent that started this activity. */
@@ -3646,6 +3651,22 @@
         return false;
     }
 
+    private static final class RequestFinishCallback extends IRequestFinishCallback.Stub {
+        private final WeakReference<Activity> mActivityRef;
+
+        RequestFinishCallback(WeakReference<Activity> activityRef) {
+            mActivityRef = activityRef;
+        }
+
+        @Override
+        public void requestFinish() {
+            Activity activity = mActivityRef.get();
+            if (activity != null) {
+                activity.mHandler.post(activity::finishAfterTransition);
+            }
+        }
+    }
+
     /**
      * Called when the activity has detected the user's press of the back
      * key.  The default implementation simply finishes the current activity,
@@ -3671,11 +3692,7 @@
             // while at the root of the task. This call allows ActivityTaskManager
             // to intercept or defer finishing.
             ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
-                    new IRequestFinishCallback.Stub() {
-                        public void requestFinish() {
-                            mHandler.post(() -> finishAfterTransition());
-                        }
-                    });
+                    new RequestFinishCallback(new WeakReference<>(this)));
         } catch (RemoteException e) {
             finishAfterTransition();
         }
@@ -6990,6 +7007,10 @@
                 writer.print(mResumed); writer.print(" mStopped=");
                 writer.print(mStopped); writer.print(" mFinished=");
                 writer.println(mFinished);
+        writer.print(innerPrefix); writer.print("mLastDispatchedIsInMultiWindowMode=");
+                writer.print(mLastDispatchedIsInMultiWindowMode);
+                writer.print(" mLastDispatchedIsInPictureInPictureMode=");
+                writer.println(mLastDispatchedIsInPictureInPictureMode);
         writer.print(innerPrefix); writer.print("mChangingConfigurations=");
                 writer.println(mChangingConfigurations);
         writer.print(innerPrefix); writer.print("mCurrentConfig=");
@@ -8071,6 +8092,7 @@
         if (mWindow != null) {
             mWindow.onMultiWindowModeChanged();
         }
+        mLastDispatchedIsInMultiWindowMode = isInMultiWindowMode;
         onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
     }
 
@@ -8083,6 +8105,7 @@
         if (mWindow != null) {
             mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
         }
+        mLastDispatchedIsInPictureInPictureMode = isInPictureInPictureMode;
         onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
     }
 
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 1fd7e52..92aabb5 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
@@ -30,7 +31,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.TransactionTooLargeException;
-import android.view.RemoteAnimationAdapter;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -51,12 +51,12 @@
     /**
      * Verify that calling app has access to the given provider.
      */
-    public abstract String checkContentProviderAccess(String authority, int userId);
+    public abstract String checkContentProviderAccess(String authority, @UserIdInt int userId);
 
     /**
      * Verify that calling UID has access to the given provider.
      */
-    public abstract int checkContentProviderUriPermission(Uri uri, int userId,
+    public abstract int checkContentProviderUriPermission(Uri uri, @UserIdInt int userId,
             int callingUid, int modeFlags);
 
     // Called by the power manager.
@@ -71,7 +71,7 @@
     /**
      * Kill foreground apps from the specified user.
      */
-    public abstract void killForegroundAppsForUser(int userHandle);
+    public abstract void killForegroundAppsForUser(@UserIdInt int userId);
 
     /**
      *  Sets how long a {@link PendingIntent} can be temporarily whitelist to by bypass restrictions
@@ -174,7 +174,7 @@
      * Checks to see if the calling pid is allowed to handle the user. Returns adjusted user id as
      * needed.
      */
-    public abstract int handleIncomingUser(int callingPid, int callingUid, int userId,
+    public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId,
             boolean allowAll, int allowMode, String name, String callerPackage);
 
     /** Checks if the calling binder pid as the permission. */
@@ -184,7 +184,7 @@
     public abstract int getCurrentUserId();
 
     /** Returns true if the user is running. */
-    public abstract boolean isUserRunning(int userId, int flags);
+    public abstract boolean isUserRunning(@UserIdInt int userId, int flags);
 
     /** Trims memory usage in the system by removing/stopping unused application processes. */
     public abstract void trimApplications();
@@ -211,7 +211,7 @@
      * @param started
      */
     public abstract void updateBatteryStats(
-            ComponentName activity, int uid, int userId, boolean resumed);
+            ComponentName activity, int uid, @UserIdInt int userId, boolean resumed);
 
     /**
      * Update UsageStats of the activity.
@@ -222,23 +222,23 @@
      * @param taskRoot TaskRecord's root
      */
     public abstract void updateActivityUsageStats(
-            ComponentName activity, int userId, int event, IBinder appToken,
+            ComponentName activity, @UserIdInt int userId, int event, IBinder appToken,
             ComponentName taskRoot);
     public abstract void updateForegroundTimeIfOnBattery(
             String packageName, int uid, long cpuTimeDiff);
-    public abstract void sendForegroundProfileChanged(int userId);
+    public abstract void sendForegroundProfileChanged(@UserIdInt int userId);
 
     /**
      * Returns whether the given user requires credential entry at this time. This is used to
      * intercept activity launches for work apps when the Work Challenge is present.
      */
-    public abstract boolean shouldConfirmCredentials(int userId);
+    public abstract boolean shouldConfirmCredentials(@UserIdInt int userId);
 
     public abstract int[] getCurrentProfileIds();
     public abstract UserInfo getCurrentUser();
-    public abstract void ensureNotSpecialUser(int userId);
-    public abstract boolean isCurrentProfile(int userId);
-    public abstract boolean hasStartedUserState(int userId);
+    public abstract void ensureNotSpecialUser(@UserIdInt int userId);
+    public abstract boolean isCurrentProfile(@UserIdInt int userId);
+    public abstract boolean hasStartedUserState(@UserIdInt int userId);
     public abstract void finishUserSwitch(Object uss);
 
     /** Schedule the execution of all pending app GCs. */
@@ -261,15 +261,16 @@
     public abstract int broadcastIntentInPackage(String packageName, int uid, int realCallingUid,
             int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras, String requiredPermission,
-            Bundle bOptions, boolean serialized, boolean sticky, int userId,
+            Bundle bOptions, boolean serialized, boolean sticky, @UserIdInt int userId,
             boolean allowBackgroundActivityStarts);
     public abstract ComponentName startServiceInPackage(int uid, Intent service,
-            String resolvedType, boolean fgRequired, String callingPackage, int userId,
+            String resolvedType, boolean fgRequired, String callingPackage, @UserIdInt int userId,
             boolean allowBackgroundActivityStarts) throws TransactionTooLargeException;
 
     public abstract void disconnectActivityFromServices(Object connectionHolder, Object conns);
-    public abstract void cleanUpServices(int userId, ComponentName component, Intent baseIntent);
-    public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId);
+    public abstract void cleanUpServices(@UserIdInt int userId, ComponentName component,
+            Intent baseIntent);
+    public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, @UserIdInt int userId);
     public abstract void ensureBootCompleted();
     public abstract void updateOomLevelsForDisplay(int displayId);
     public abstract boolean isActivityStartsLoggingEnabled();
@@ -328,7 +329,7 @@
     public abstract boolean isAppBad(ApplicationInfo info);
 
     /** Remove pending backup for the given userId. */
-    public abstract void clearPendingBackup(int userId);
+    public abstract void clearPendingBackup(@UserIdInt int userId);
 
     /**
      * When power button is very long pressed, call this interface to do some pre-shutdown work
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 4ef554d..ac8c9f4 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -202,21 +202,6 @@
     }
 
     /**
-     * Resizes the input stack id to the given bounds.
-     * @param stackId Id of the stack to resize.
-     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     */
-    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public void resizeStack(int stackId, Rect bounds) throws SecurityException {
-        try {
-            getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
-                    false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Removes stacks in the windowing modes from the system if they are of activity type
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
@@ -362,10 +347,13 @@
      * @param animate Whether we should play an animation for resizing stack.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public void resizeStack(int stackId, Rect bounds, boolean animate) {
+    public void resizePinnedStack(int stackId, Rect bounds, boolean animate) {
         try {
-            getService().resizeStack(stackId, bounds, false, false, animate /* animate */,
-                    -1 /* animationDuration */);
+            if (animate) {
+                getService().animateResizePinnedStack(stackId, bounds, -1 /* animationDuration */);
+            } else {
+                getService().resizePinnedStack(bounds, null /* tempPinnedTaskBounds */);
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a6784780..1cc8499 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3983,24 +3983,14 @@
                 data.info.applicationInfo, data.compatInfo);
         Service service = null;
         try {
-            java.lang.ClassLoader cl = packageInfo.getClassLoader();
-            service = packageInfo.getAppFactory()
-                    .instantiateService(cl, data.info.name, data.intent);
-        } catch (Exception e) {
-            if (!mInstrumentation.onException(service, e)) {
-                throw new RuntimeException(
-                    "Unable to instantiate service " + data.info.name
-                    + ": " + e.toString(), e);
-            }
-        }
-
-        try {
             if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
 
             ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
-            context.setOuterContext(service);
-
             Application app = packageInfo.makeApplication(false, mInstrumentation);
+            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            service = packageInfo.getAppFactory()
+                    .instantiateService(cl, data.info.name, data.intent);
+            context.setOuterContext(service);
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
             service.onCreate();
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 3a95839..0f9a6e6 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -197,13 +197,21 @@
         mHasExited = false;
         ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
         ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
-        if (mEnterActivityOptions.isReturning()) {
+        final boolean isReturning = mEnterActivityOptions.isReturning();
+        if (isReturning) {
             restoreExitedViews();
             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
         }
         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
                 resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
-                mEnterActivityOptions.isCrossTask());
+                mEnterActivityOptions.isCrossTask(),
+                () -> {
+                    if (isReturning) {
+                        // once it is done transitioning, we don't need the coordinator --
+                        // if we kept it around, it could leak Views
+                        mEnterTransitionCoordinator = null;
+                    }
+                });
         if (mEnterActivityOptions.isCrossTask()) {
             mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
             mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
diff --git a/core/java/android/app/AppCompatCallbacks.java b/core/java/android/app/AppCompatCallbacks.java
index 17697db..08c97eb 100644
--- a/core/java/android/app/AppCompatCallbacks.java
+++ b/core/java/android/app/AppCompatCallbacks.java
@@ -19,6 +19,9 @@
 import android.compat.Compatibility;
 import android.os.Process;
 import android.util.Log;
+import android.util.StatsLog;
+
+import com.android.internal.compat.ChangeReporter;
 
 import java.util.Arrays;
 
@@ -28,10 +31,10 @@
  * @hide
  */
 public final class AppCompatCallbacks extends Compatibility.Callbacks {
-
     private static final String TAG = "Compatibility";
 
     private final long[] mDisabledChanges;
+    private final ChangeReporter mChangeReporter;
 
     /**
      * Install this class into the current process.
@@ -45,20 +48,29 @@
     private AppCompatCallbacks(long[] disabledChanges) {
         mDisabledChanges = Arrays.copyOf(disabledChanges, disabledChanges.length);
         Arrays.sort(mDisabledChanges);
+        mChangeReporter = new ChangeReporter();
     }
 
     protected void reportChange(long changeId) {
-        Log.d(TAG, "Compat change reported: " + changeId + "; UID " + Process.myUid());
-        // TODO log via StatsLog
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     protected boolean isChangeEnabled(long changeId) {
         if (Arrays.binarySearch(mDisabledChanges, changeId) < 0) {
             // Not present in the disabled array
-            reportChange(changeId);
+            reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
+    private void reportChange(long changeId, int state) {
+        int uid = Process.myUid();
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Log.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__APP_PROCESS);
+    }
+
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9353e0b..1649e8b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -28,12 +28,19 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.usage.UsageStatsManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.database.DatabaseUtils;
 import android.media.AudioAttributes.AttributeUsage;
 import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
@@ -48,9 +55,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.Immutable;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.ZygoteInit;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
@@ -58,10 +68,12 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -117,6 +129,12 @@
     private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
             new ArrayMap<>();
 
+    private static final Object sLock = new Object();
+
+    /** Current {@link AppOpsCollector}. Change via {@link #setNotedAppOpsCollector} */
+    @GuardedBy("sLock")
+    private static @Nullable AppOpsCollector sNotedAppOpsCollector;
+
     static IBinder sToken;
 
     /** @hide */
@@ -835,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";
@@ -1116,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
@@ -1125,6 +1147,22 @@
     /** @hide Query all packages on device */
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
 
+
+    /** {@link #sAppOpsToNote} not initialized yet for this op */
+    private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
+    /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
+    private static final byte SHOULD_NOT_COLLECT_NOTE_OP = 1;
+    /** Should collect noting of this app-op in {@link #sAppOpsToNote} */
+    private static final byte SHOULD_COLLECT_NOTE_OP = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "SHOULD_" }, value = {
+            SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED,
+            SHOULD_NOT_COLLECT_NOTE_OP,
+            SHOULD_COLLECT_NOTE_OP
+    })
+    private @interface ShouldCollectNoteOp {}
+
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
     private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
@@ -1146,6 +1184,7 @@
             // Storage
             OP_READ_EXTERNAL_STORAGE,
             OP_WRITE_EXTERNAL_STORAGE,
+            OP_ACCESS_MEDIA_LOCATION,
             // Location
             OP_COARSE_LOCATION,
             OP_FINE_LOCATION,
@@ -1184,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
     };
 
     /**
@@ -1285,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
     };
 
@@ -1382,6 +1425,7 @@
             OPSTR_LEGACY_STORAGE,
             OPSTR_ACCESS_ACCESSIBILITY,
             OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_ACCESS_MEDIA_LOCATION,
             OPSTR_QUERY_ALL_PACKAGES,
     };
 
@@ -1480,6 +1524,7 @@
             "LEGACY_STORAGE",
             "ACCESS_ACCESSIBILITY",
             "READ_DEVICE_IDENTIFIERS",
+            "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
     };
 
@@ -1564,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,
@@ -1579,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
     };
 
@@ -1678,6 +1724,7 @@
             null, // LEGACY_STORAGE
             null, // ACCESS_ACCESSIBILITY
             null, // READ_DEVICE_IDENTIFIERS
+            null, // ACCESS_MEDIA_LOCATION
             null, // QUERY_ALL_PACKAGES
     };
 
@@ -1776,6 +1823,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1873,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
     };
 
@@ -1974,6 +2023,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // ACCESS_MEDIA_LOCATION
             false, // QUERY_ALL_PACKAGES
     };
 
@@ -1987,6 +2037,27 @@
      */
     private static HashMap<String, Integer> sPermToOp = new HashMap<>();
 
+    /**
+     * Set to the uid of the caller if this thread is currently executing a two-way binder
+     * transaction. Not set if this thread is currently not executing a two way binder transaction.
+     *
+     * @see #startNotedAppOpsCollection
+     * @see #markAppOpNoted
+     */
+    private static final ThreadLocal<Integer> sBinderThreadCallingUid = new ThreadLocal<>();
+
+    /**
+     * If a thread is currently executing a two-way binder transaction, this stores the op-codes of
+     * the app-ops that were noted during this transaction.
+     *
+     * @see #markAppOpNoted
+     */
+    private static final ThreadLocal<long[]> sAppOpsNotedInThisBinderTransaction =
+            new ThreadLocal<>();
+
+    /** Whether noting for an appop should be collected */
+    private static final @ShouldCollectNoteOp byte[] sAppOpsToNote = new byte[_NUM_OP];
+
     static {
         if (sOpToSwitch.length != _NUM_OP) {
             throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
@@ -2030,6 +2101,12 @@
                 sPermToOp.put(sOpPerms[op], op);
             }
         }
+
+        if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
+            // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
+            // two longs
+            throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
+        }
     }
 
     /** @hide */
@@ -2607,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);
         }
 
@@ -2629,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);
         }
 
@@ -4190,8 +4267,8 @@
      * end UID states.
      *
      * @param counts The data array.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
      * @return The sum.
      */
@@ -4220,13 +4297,13 @@
      * end UID states.
      *
      * @param counts The data array.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
      * @return The non-negative value or -1.
      */
     private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts,
-            @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) {
+            @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
         if (counts == null) {
             return -1;
         }
@@ -4252,14 +4329,14 @@
      * end UID states.
      *
      * @param counts The data array.
+     * @param beginUidState The beginning UID state (inclusive).
+     * @param endUidState The end UID state (inclusive).
      * @param flags The UID flags.
-     * @param beginUidState The beginning UID state (exclusive).
-     * @param endUidState The end UID state.
      * @return The non-negative value or -1.
      */
     private static @Nullable String findFirstNonNullForFlagsInStates(
-            @Nullable LongSparseArray<String> counts, @OpFlags int flags,
-            @UidState int beginUidState, @UidState int endUidState) {
+            @Nullable LongSparseArray<String> counts, @UidState int beginUidState,
+            @UidState int endUidState, @OpFlags int flags) {
         if (counts == null) {
             return null;
         }
@@ -4350,8 +4427,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
@@ -4374,8 +4451,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param ops The set of operations you are interested in, or null if you want all of them.
      * @hide
@@ -4396,8 +4473,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param uid The uid of the application of interest.
      * @param packageName The name of the application of interest.
@@ -4429,8 +4506,8 @@
      * The mode of the ops returned are set for the package but may not reflect their effective
      * state due to UID policy or because it's controlled by a different master op.
      *
-     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
-     * if the effective mode is needed.
+     * Use {@link #unsafeCheckOp(String, int, String)}} or
+     * {@link #noteOp(String, int, String, String)} if the effective mode is needed.
      *
      * @param uid The uid of the application of interest.
      * @param packageName The name of the application of interest.
@@ -4820,7 +4897,7 @@
      *
      * @see #isOperationActive
      * @see #stopWatchingActive
-     * @see #startOp(int, int, String)
+     * @see #startOp(int, int, String, boolean, String)
      * @see #finishOp(int, int, String)
      */
     // TODO: Uncomment below annotation once b/73559440 is fixed
@@ -4871,7 +4948,7 @@
      *
      * @see #isOperationActive
      * @see #startWatchingActive
-     * @see #startOp(int, int, String)
+     * @see #startOp(int, int, String, boolean, String)
      * @see #finishOp(int, int, String)
      */
     public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
@@ -4902,7 +4979,7 @@
      *
      * @see #startWatchingActive(int[], OnOpActiveChangedListener)
      * @see #stopWatchingNoted(OnOpNotedListener)
-     * @see #noteOp(String, int, String)
+     * @see #noteOp(String, int, String, String)
      *
      * @hide
      */
@@ -4934,7 +5011,7 @@
      * Unregistering a non-registered callback has no effect.
      *
      * @see #startWatchingNoted(int[], OnOpNotedListener)
-     * @see #noteOp(String, int, String)
+     * @see #noteOp(String, int, String, String)
      *
      * @hide
      */
@@ -4969,15 +5046,16 @@
 
     /**
      * Do a quick check for whether an application might be able to perform an operation.
-     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)}
-     * or {@link #startOp(String, int, String)} for your actual security checks, which also
-     * ensure that the given uid and package name are consistent.  This function can just be
-     * used for a quick check to see if an operation has been disabled for the application,
+     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+     * String)} or {@link #startOp(String, int, String, String)} for your actual security checks,
+     * which also ensure that the given uid and package name are consistent. This function can just
+     * be used for a quick check to see if an operation has been disabled for the application,
      * as an early reject of some work.  This does not modify the time stamp or other data
      * about the operation.
      *
      * <p>Important things this will not do (which you need to ultimate use
-     * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+     * {@link #noteOp(String, int, String, String)} or
+     * {@link #startOp(String, int, String, String)} to cover):</p>
      * <ul>
      *     <li>Verifying the uid and package are consistent, so callers can't spoof
      *     their identity.</li>
@@ -5048,126 +5126,305 @@
     }
 
     /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOp(op, uid, packageName, null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    @Deprecated
+    public int noteOp(int op) {
+        return noteOp(op, Process.myUid(), mContext.getOpPackageName(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOp(String, int, String, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOp(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    public int noteOp(int op, int uid, @Nullable String packageName) {
+        return noteOp(op, uid, packageName, null);
+    }
+
+    /**
      * Make note of an application performing an operation.  Note that you must pass
      * in both the uid and name of the application to be checked; this function will verify
      * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
+     *
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
-        return noteOp(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     */
-    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
-        return noteOpNoThrow(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Make note of an application performing an operation on behalf of another
-     * application when handling an IPC. Note that you must pass the package name
-     * of the application that is being proxied while its UID will be inferred from
-     * the IPC state; this function will verify that the calling uid and proxied
-     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
-     * succeeds, the last execution time of the operation for the proxied app and
-     * your app will be updated to the current time.
-     * @param op The operation to note.  One of the OPSTR_* constants.
-     * @param proxiedPackageName The name of the application calling into the proxy application.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
-        return noteProxyOp(strOpToOp(op), proxiedPackageName);
-    }
-
-    /**
-     * Like {@link #noteProxyOp(String, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     * @param message A message describing the reason the op was noted
      *
-     * <p>This API requires the package with the {@code proxiedPackageName} to belongs to
-     * {@link Binder#getCallingUid()}.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op.
      */
-    public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
-        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName);
+    public int noteOp(@NonNull String op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        return noteOp(strOpToOp(op), uid, packageName, message);
     }
 
     /**
-     * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid.
+     * Make note of an application performing an operation.  Note that you must pass
+     * in both the uid and name of the application to be checked; this function will verify
+     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time.
+     *
+     * @param op The operation to note.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op.
+     *
+     * @hide
+     */
+    public int noteOp(int op, int uid, @Nullable String packageName, @Nullable String message) {
+        final int mode = noteOpNoThrow(op, uid, packageName, message);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+        }
+        return mode;
+    }
+
+    /**
+     * @deprecated Use {@link #noteOpNoThrow(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOpNoThrow(op, uid, packageName, null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteOpNoThrow(int, int, String, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteOpNoThrow(java.lang.String, int, java.lang.String, java.lang.String)} instead")
+    public int noteOpNoThrow(int op, int uid, String packageName) {
+        return noteOpNoThrow(op, uid, packageName, null);
+    }
+
+    /**
+     * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to note.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     */
+    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+            @Nullable String message) {
+        return noteOpNoThrow(strOpToOp(op), uid, packageName, message);
+    }
+
+    /**
+     * Like {@link #noteOp(String, int, String, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to note.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @hide
+     */
+    public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        try {
+            int mode = mService.noteOperation(op, uid, packageName);
+            if (mode == MODE_ALLOWED) {
+                markAppOpNoted(uid, packageName, op, message);
+            }
+
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOp(String, String, int, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
+            + "#noteProxyOp(java.lang.String, java.lang.String, int, java.lang.String)} instead")
+    public int noteProxyOp(int op, @Nullable String proxiedPackageName) {
+        return noteProxyOp(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another application when
+     * handling an IPC. This function will verify that the calling uid and proxied package name
+     * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+     * time of the operation for the proxied app and your app will be updated to the current time.
+     *
+     * @param op The operation to note. One of the OP_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @param proxiedUid The uid of the proxied application
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+     * if it is not allowed and should be silently ignored (without causing the app to crash).
+     *
+     * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+     * op.
+     *
+     * @hide
+     */
+    public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        int mode = noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, message);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+                    + " from uid " + Process.myUid() + " or calling package " + proxiedPackageName
+                    + " from uid " + proxiedUid + " not allowed to perform " + sOpNames[op]);
+        }
+        return mode;
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another application when
+     * handling an IPC. This function will verify that the calling uid and proxied package name
+     * match, and if not, return {@link #MODE_IGNORED}. If this call succeeds, the last execution
+     * time of the operation for the proxied app and your app will be updated to the current time.
+     *
+     * @param op The operation to note. One of the OPSTR_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @param proxiedUid The uid of the proxied application
+     * @param message A message describing the reason the op was noted
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or {@link #MODE_IGNORED}
+     * if it is not allowed and should be silently ignored (without causing the app to crash).
+     *
+     * @throws SecurityException If the proxy or proxied app has been configured to crash on this
+     * op.
+     */
+    public int noteProxyOp(@NonNull String op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        return noteProxyOp(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid(), null);
+    }
+
+    /**
+     * @deprecated Use {@link #noteProxyOpNoThrow(String, String, int, String)} instead
+     */
+    @Deprecated
+    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+            int proxiedUid) {
+        return noteProxyOpNoThrow(op, proxiedPackageName, proxiedUid, null);
+    }
+
+    /**
+     * Like {@link #noteProxyOp(String, String, int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
      *
      * <p>This API requires package with the {@code proxiedPackageName} to belong to
      * {@code proxiedUid}.
      *
      * @param op The op to note
+     * @param proxiedPackageName The package to note the op for
+     * @param proxiedUid The uid the package belongs to
+     * @param message A message describing the reason the op was noted
+     */
+    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+            int proxiedUid, @Nullable String message) {
+        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid, message);
+    }
+
+    /**
+     * Like {@link #noteProxyOp(int, String, int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The op to note
      * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
      *                           noted for the "android" package
      * @param proxiedUid The uid the package belongs to
+     * @param message A message describing the reason the op was noted
+     *
+     * @hide
      */
-    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
-            int proxiedUid) {
-        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid);
-    }
+    public int noteProxyOpNoThrow(int op, @Nullable String proxiedPackageName, int proxiedUid,
+            @Nullable String message) {
+        int myUid = Process.myUid();
 
-    /**
-     * Report that an application has started executing a long-running operation.  Note that you
-     * must pass in both the uid and name of the application to be checked; this function will
-     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time and the operation will be marked as "running".  In this case you must
-     * later call {@link #finishOp(String, int, String)} to report when the application is no
-     * longer performing the operation.
-     * @param op The operation to start.  One of the OPSTR_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     */
-    public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
-        return startOp(strOpToOp(op), uid, packageName);
-    }
+        try {
+            int mode = mService.noteProxyOperation(op, myUid, mContext.getOpPackageName(),
+                    proxiedUid, proxiedPackageName);
+            if (mode == MODE_ALLOWED
+                    // Only collect app-ops when the proxy is trusted
+                    && mContext.checkPermission(Manifest.permission.UPDATE_APP_OPS_STATS, -1, myUid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                markAppOpNoted(proxiedUid, proxiedPackageName, op, message);
+            }
 
-    /**
-     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     */
-    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
-        return startOpNoThrow(strOpToOp(op), uid, packageName);
-    }
-
-    /**
-     * Report that an application is no longer performing an operation that had previously
-     * been started with {@link #startOp(String, int, String)}.  There is no validation of input
-     * or result; the parameters supplied here must be the exact same ones previously passed
-     * in when starting the operation.
-     */
-    public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
-        finishOp(strOpToOp(op), uid, packageName);
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
      * Do a quick check for whether an application might be able to perform an operation.
-     * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
-     * or {@link #startOp(int, int, String)} for your actual security checks, which also
-     * ensure that the given uid and package name are consistent.  This function can just be
-     * used for a quick check to see if an operation has been disabled for the application,
-     * as an early reject of some work.  This does not modify the time stamp or other data
-     * about the operation.
+     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
+     * String)} or {@link #startOp(int, int, String, boolean, String)} for your actual security
+     * checks, which also ensure that the given uid and package name are consistent. This function
+     * can just be used for a quick check to see if an operation has been disabled for the
+     * application, as an early reject of some work.  This does not modify the time stamp or other
+     * data about the operation.
      *
      * <p>Important things this will not do (which you need to ultimate use
-     * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+     * {@link #noteOp(String, int, String, String)} or
+     * {@link #startOp(int, int, String, boolean, String)} to cover):</p>
      * <ul>
      *     <li>Verifying the uid and package are consistent, so callers can't spoof
      *     their identity.</li>
@@ -5259,107 +5516,6 @@
         }
     }
 
-    /**
-     * Make note of an application performing an operation.  Note that you must pass
-     * in both the uid and name of the application to be checked; this function will verify
-     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time.
-     * @param op The operation to note.  One of the OP_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteOp(int op, int uid, String packageName) {
-        final int mode = noteOpNoThrow(op, uid, packageName);
-        if (mode == MODE_ERRORED) {
-            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
-        }
-        return mode;
-    }
-
-    /**
-     * Make note of an application performing an operation on behalf of another
-     * application when handling an IPC. Note that you must pass the package name
-     * of the application that is being proxied while its UID will be inferred from
-     * the IPC state; this function will verify that the calling uid and proxied
-     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
-     * succeeds, the last execution time of the operation for the proxied app and
-     * your app will be updated to the current time.
-     * @param op The operation to note. One of the OPSTR_* constants.
-     * @param proxiedPackageName The name of the application calling into the proxy application.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the proxy or proxied app has been configured to
-     * crash on this op.
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteProxyOp(int op, String proxiedPackageName) {
-        int mode = noteProxyOpNoThrow(op, proxiedPackageName);
-        if (mode == MODE_ERRORED) {
-            throw new SecurityException("Proxy package " + mContext.getOpPackageName()
-                    + " from uid " + Process.myUid() + " or calling package "
-                    + proxiedPackageName + " from uid " + Binder.getCallingUid()
-                    + " not allowed to perform " + sOpNames[op]);
-        }
-        return mode;
-    }
-
-    /**
-     * Like {@link #noteProxyOp(int, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
-     * @hide
-     */
-    public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) {
-        try {
-            return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(),
-                    proxiedUid, proxiedPackageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Like {@link #noteProxyOp(int, String)} but instead
-     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
-     *
-     * <p>This API requires the package with {@code proxiedPackageName} to belongs to
-     * {@link Binder#getCallingUid()}.
-     *
-     * @hide
-     */
-    public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
-        return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid());
-    }
-
-    /**
-     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public int noteOpNoThrow(int op, int uid, String packageName) {
-        try {
-            return mService.noteOperation(op, uid, packageName);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /** @hide */
-    @UnsupportedAppUsage
-    public int noteOp(int op) {
-        return noteOp(op, Process.myUid(), mContext.getOpPackageName());
-    }
-
     /** @hide */
     @UnsupportedAppUsage
     public static IBinder getToken(IAppOpsService service) {
@@ -5376,54 +5532,86 @@
         }
     }
 
-    /** @hide */
-    public int startOp(int op) {
-        return startOp(op, Process.myUid(), mContext.getOpPackageName());
+
+    /**
+     * @deprecated use {@link #startOp(String, int, String, String)} instead
+     */
+    @Deprecated
+    public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOp(op, uid, packageName, null);
     }
 
     /**
-     * Report that an application has started executing a long-running operation.  Note that you
-     * must pass in both the uid and name of the application to be checked; this function will
-     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
-     * succeeds, the last execution time of the operation for this app will be updated to
-     * the current time and the operation will be marked as "running".  In this case you must
-     * later call {@link #finishOp(int, int, String)} to report when the application is no
-     * longer performing the operation.
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
      *
-     * @param op The operation to start.  One of the OP_* constants.
-     * @param uid The user id of the application attempting to perform the operation.
-     * @param packageName The name of the application attempting to perform the operation.
-     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
-     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
-     * causing the app to crash).
-     * @throws SecurityException If the app has been configured to crash on this op.
      * @hide
      */
-    public int startOp(int op, int uid, String packageName) {
-        return startOp(op, uid, packageName, false);
+    @Deprecated
+    public int startOp(int op) {
+        return startOp(op, Process.myUid(), mContext.getOpPackageName(), false, null);
     }
 
     /**
-     * Report that an application has started executing a long-running operation. Similar
-     * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
-     * the operation should succeed since the caller has performed its standard permission
-     * checks which passed and would perform the protected operation for this mode.
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOp(int op, int uid, String packageName) {
+        return startOp(op, uid, packageName, false, null);
+    }
+
+    /**
+     * @deprecated Use {@link #startOp(int, int, String, boolean, String)} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+        return startOp(op, uid, packageName, startIfModeDefault, null);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.
+     *
+     * @param op The operation to start.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @param message Description why op was started
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     *
+     * @throws SecurityException If the app has been configured to crash on this op or
+     * the package is not in the passed in UID.
+     */
+    public int startOp(@NonNull String op, int uid, @Nullable String packageName,
+            @Nullable String message) {
+        return startOp(strOpToOp(op), uid, packageName, false, message);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.
      *
      * @param op The operation to start.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
+     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     * @param message Description why op was started
+     *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
      * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
      * causing the app to crash).
-     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
      *
      * @throws SecurityException If the app has been configured to crash on this op or
      * the package is not in the passed in UID.
      *
      * @hide
      */
-    public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
-        final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+    public int startOp(int op, int uid, @Nullable String packageName, boolean startIfModeDefault,
+            @Nullable String message) {
+        final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault, message);
         if (mode == MODE_ERRORED) {
             throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
         }
@@ -5431,45 +5619,111 @@
     }
 
     /**
-     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
-     * returns {@link #MODE_ERRORED}.
-     * @hide
+     * @deprecated use {@link #startOpNoThrow(String, int, String, String)} instead
      */
-    public int startOpNoThrow(int op, int uid, String packageName) {
-        return startOpNoThrow(op, uid, packageName, false);
+    @Deprecated
+    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOpNoThrow(op, uid, packageName, null);
     }
 
     /**
-     * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+     * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOpNoThrow(int op, int uid, String packageName) {
+        return startOpNoThrow(op, uid, packageName, false, null);
+    }
+
+    /**
+     * @deprecated Use {@link #startOpNoThrow(int, int, String, boolean, String} instead
+     *
+     * @hide
+     */
+    @Deprecated
+    public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+        return startOpNoThrow(op, uid, packageName, startIfModeDefault, null);
+    }
+
+    /**
+     * Like {@link #startOp(String, int, String, String)} but instead of throwing a
      * {@link SecurityException} it returns {@link #MODE_ERRORED}.
      *
      * @param op The operation to start.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
+     * @param message Description why op was started
+     *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
      * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
      * causing the app to crash).
+     */
+    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
+            @Nullable String message) {
+        return startOpNoThrow(strOpToOp(op), uid, packageName, false, message);
+    }
+
+    /**
+     * Like {@link #startOp(int, int, String, boolean, String)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to start.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
      * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     * @param message Description why op was started
+     *
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
      *
      * @hide
      */
-    public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+    public int startOpNoThrow(int op, int uid, @NonNull String packageName,
+            boolean startIfModeDefault, @Nullable String message) {
         try {
-            return mService.startOperation(getToken(mService), op, uid, packageName,
+            int mode = mService.startOperation(getToken(mService), op, uid, packageName,
                     startIfModeDefault);
+            if (mode == MODE_ALLOWED) {
+                markAppOpNoted(uid, packageName, op, message);
+            }
+
+            return mode;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Report that an application is no longer performing an operation that had previously
-     * been started with {@link #startOp(int, int, String)}.  There is no validation of input
-     * or result; the parameters supplied here must be the exact same ones previously passed
-     * in when starting the operation.
+     * @deprecated Use {@link #finishOp(String, int, String)} instead
+     *
      * @hide
      */
-    public void finishOp(int op, int uid, String packageName) {
+    @Deprecated
+    public void finishOp(int op) {
+        finishOp(op, Process.myUid(), mContext.getOpPackageName());
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(String, int, String, String)}.  There is no validation of
+     * input or result; the parameters supplied here must be the exact same ones previously passed
+     * in when starting the operation.
+     */
+    public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
+        finishOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(int, int, String, boolean, String)}. There is no
+     * validation of input or result; the parameters supplied here must be the exact same ones
+     * previously passed in when starting the operation.
+     *
+     * @hide
+     */
+    public void finishOp(int op, int uid, @NonNull String packageName) {
         try {
             mService.finishOperation(getToken(mService), op, uid, packageName);
         } catch (RemoteException e) {
@@ -5477,25 +5731,402 @@
         }
     }
 
-    /** @hide */
-    public void finishOp(int op) {
-        finishOp(op, Process.myUid(), mContext.getOpPackageName());
-    }
-
     /**
      * Checks whether the given op for a package is active.
      * <p>
      * If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
      * permission you can query only for your UID.
      *
-     * @see #finishOp(int)
-     * @see #startOp(int)
+     * @see #finishOp(String, int, String)
+     * @see #startOp(String, int, String, String)
      */
     public boolean isOpActive(@NonNull String op, int uid, @NonNull String packageName) {
         return isOperationActive(strOpToOp(op), uid, packageName);
     }
 
     /**
+     * Start collection of noted appops on this thread.
+     *
+     * <p>Called at the beginning of a two way binder transaction.
+     *
+     * @see #finishNotedAppOpsCollection()
+     *
+     * @hide
+     */
+    public static void startNotedAppOpsCollection(int callingUid) {
+        sBinderThreadCallingUid.set(callingUid);
+    }
+
+    /**
+     * State of a temporarily paused noted app-ops collection.
+     *
+     * @see #pauseNotedAppOpsCollection()
+     *
+     * @hide
+     */
+    public static class PausedNotedAppOpsCollection {
+        final int mUid;
+        final @Nullable long[] mCollectedNotedAppOps;
+
+        PausedNotedAppOpsCollection(int uid, @Nullable long[] collectedNotedAppOps) {
+            mUid = uid;
+            mCollectedNotedAppOps = collectedNotedAppOps;
+        }
+    }
+
+    /**
+     * Temporarily suspend collection of noted app-ops when binder-thread calls into the other
+     * process. During such a call there might be call-backs coming back on the same thread which
+     * should not be accounted to the current collection.
+     *
+     * @return a state needed to resume the collection
+     *
+     * @hide
+     */
+    public static @Nullable PausedNotedAppOpsCollection pauseNotedAppOpsCollection() {
+        Integer previousUid = sBinderThreadCallingUid.get();
+        if (previousUid != null) {
+            long[] previousCollectedNotedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+
+            sBinderThreadCallingUid.remove();
+            sAppOpsNotedInThisBinderTransaction.remove();
+
+            return new PausedNotedAppOpsCollection(previousUid, previousCollectedNotedAppOps);
+        }
+
+        return null;
+    }
+
+    /**
+     * Resume a collection paused via {@link #pauseNotedAppOpsCollection}.
+     *
+     * @param prevCollection The state of the previous collection
+     *
+     * @hide
+     */
+    public static void resumeNotedAppOpsCollection(
+            @Nullable PausedNotedAppOpsCollection prevCollection) {
+        if (prevCollection != null) {
+            sBinderThreadCallingUid.set(prevCollection.mUid);
+
+            if (prevCollection.mCollectedNotedAppOps != null) {
+                sAppOpsNotedInThisBinderTransaction.set(prevCollection.mCollectedNotedAppOps);
+            }
+        }
+    }
+
+    /**
+     * Finish collection of noted appops on this thread.
+     *
+     * <p>Called at the end of a two way binder transaction.
+     *
+     * @see #startNotedAppOpsCollection(int)
+     *
+     * @hide
+     */
+    public static void finishNotedAppOpsCollection() {
+        sBinderThreadCallingUid.remove();
+        sAppOpsNotedInThisBinderTransaction.remove();
+    }
+
+    /**
+     * Mark an app-op as noted
+     */
+    private void markAppOpNoted(int uid, @NonNull String packageName, int code,
+            @Nullable String message) {
+        // check it the appops needs to be collected and cache result
+        if (sAppOpsToNote[code] == SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED) {
+            boolean shouldCollectNotes;
+            try {
+                shouldCollectNotes = mService.shouldCollectNotes(code);
+            } catch (RemoteException e) {
+                return;
+            }
+
+            if (shouldCollectNotes) {
+                sAppOpsToNote[code] = SHOULD_COLLECT_NOTE_OP;
+            } else {
+                sAppOpsToNote[code] = SHOULD_NOT_COLLECT_NOTE_OP;
+            }
+        }
+
+        if (sAppOpsToNote[code] != SHOULD_COLLECT_NOTE_OP) {
+            return;
+        }
+
+        Integer binderUid = sBinderThreadCallingUid.get();
+
+        synchronized (sLock) {
+            if (sNotedAppOpsCollector != null && uid == Process.myUid() && packageName.equals(
+                    ActivityThread.currentOpPackageName())) {
+                // This app is noting an app-op for itself. Deliver immediately.
+                sNotedAppOpsCollector.onSelfNoted(new SyncNotedAppOp(code));
+
+                return;
+            }
+        }
+
+        if (binderUid != null && binderUid == uid) {
+            // If this is inside of a two-way binder call: Delivered to caller via
+            // {@link #prefixParcelWithAppOpsIfNeeded}
+            long[] appOpsNotedInThisBinderTransaction;
+
+            appOpsNotedInThisBinderTransaction = sAppOpsNotedInThisBinderTransaction.get();
+            if (appOpsNotedInThisBinderTransaction == null) {
+                appOpsNotedInThisBinderTransaction = new long[2];
+                sAppOpsNotedInThisBinderTransaction.set(appOpsNotedInThisBinderTransaction);
+            }
+
+            if (code < 64) {
+                appOpsNotedInThisBinderTransaction[0] |= 1L << code;
+            } else {
+                appOpsNotedInThisBinderTransaction[1] |= 1L << (code - 64);
+            }
+        } else {
+            // Cannot deliver the note synchronous: Hence send it to the system server to
+            // notify the noted process.
+            if (message == null) {
+                // Default message is a stack trace
+                message = getFormattedStackTrace();
+            }
+
+            long token = Binder.clearCallingIdentity();
+            try {
+                mService.noteAsyncOp(mContext.getOpPackageName(), uid, packageName, code, message);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /**
+     * Append app-ops noted in the current two-way binder transaction to parcel.
+     *
+     * <p>This is called on the callee side of a two way binder transaction just before the
+     * transaction returns.
+     *
+     * @param p the parcel to append the noted app-ops to
+     *
+     * @hide
+     */
+    public static void prefixParcelWithAppOpsIfNeeded(@NonNull Parcel p) {
+        long[] notedAppOps = sAppOpsNotedInThisBinderTransaction.get();
+        if (notedAppOps == null || (notedAppOps[0] == 0 && notedAppOps[1] == 0)) {
+            return;
+        }
+
+        p.writeInt(Parcel.EX_HAS_NOTED_APPOPS_REPLY_HEADER);
+        p.writeLong(notedAppOps[0]);
+        p.writeLong(notedAppOps[1]);
+    }
+
+    /**
+     * Read app-ops noted during a two-way binder transaction from parcel.
+     *
+     * <p>This is called on the calling side of a two way binder transaction just after the
+     * transaction returns.
+     *
+     * <p>Note: Make sure to keep frameworks/native/libs/binder/Status.cpp::readAndLogNotedAppops
+     * in sync.
+     *
+     * @param p The parcel to read from
+     *
+     * @hide
+     */
+    public static void readAndLogNotedAppops(@NonNull Parcel p) {
+        long[] rawNotedAppOps = new long[2];
+        rawNotedAppOps[0] = p.readLong();
+        rawNotedAppOps[1] = p.readLong();
+
+        if (rawNotedAppOps[0] != 0 || rawNotedAppOps[1] != 0) {
+            BitSet notedAppOps = BitSet.valueOf(rawNotedAppOps);
+
+            synchronized (sLock) {
+                for (int code = notedAppOps.nextSetBit(0); code != -1;
+                        code = notedAppOps.nextSetBit(code + 1)) {
+                    if (sNotedAppOpsCollector != null) {
+                        sNotedAppOpsCollector.onNoted(new SyncNotedAppOp(code));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Register a new {@link AppOpsCollector}.
+     *
+     * <p>There can only ever be one collector per process. If there currently is a collector
+     * registered, it will be unregistered.
+     *
+     * <p><b>Only appops related to dangerous permissions are collected.</b>
+     *
+     * @param collector The collector to set or {@code null} to unregister.
+     */
+    public void setNotedAppOpsCollector(@Nullable AppOpsCollector collector) {
+        synchronized (sLock) {
+            if (sNotedAppOpsCollector != null) {
+                try {
+                    mService.stopWatchingAsyncNoted(mContext.getPackageName(),
+                            sNotedAppOpsCollector.mAsyncCb);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+
+            sNotedAppOpsCollector = collector;
+
+            if (sNotedAppOpsCollector != null) {
+                List<AsyncNotedAppOp> missedAsyncOps = null;
+                try {
+                    mService.startWatchingAsyncNoted(mContext.getPackageName(),
+                            sNotedAppOpsCollector.mAsyncCb);
+                    missedAsyncOps = mService.extractAsyncOps(mContext.getPackageName());
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+
+                if (missedAsyncOps != null) {
+                    int numMissedAsyncOps = missedAsyncOps.size();
+                    for (int i = 0; i < numMissedAsyncOps; i++) {
+                        final AsyncNotedAppOp asyncNotedAppOp = missedAsyncOps.get(i);
+                        if (sNotedAppOpsCollector != null) {
+                            sNotedAppOpsCollector.getAsyncNotedExecutor().execute(
+                                    () -> sNotedAppOpsCollector.onAsyncNoted(
+                                            asyncNotedAppOp));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} iff the process currently is currently collecting noted appops.
+     *
+     * @see #setNotedAppOpsCollector(AppOpsCollector)
+     *
+     * @hide
+     */
+    public static boolean isCollectingNotedAppOps() {
+        synchronized (sLock) {
+            return sNotedAppOpsCollector != null;
+        }
+    }
+
+    /**
+     * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
+     * appops.
+     *
+     * <p><b>Only appops related to dangerous permissions are collected.</b>
+     */
+    public abstract static class AppOpsCollector {
+        /** Callback registered with the system. This will receive the async notes ops */
+        private final IAppOpsAsyncNotedCallback mAsyncCb = new IAppOpsAsyncNotedCallback.Stub() {
+            @Override
+            public void opNoted(AsyncNotedAppOp op) {
+                Preconditions.checkNotNull(op);
+
+                getAsyncNotedExecutor().execute(() -> onAsyncNoted(op));
+            }
+        };
+
+        /**
+         * @return The executor for the system to use when calling {@link #onAsyncNoted}.
+         */
+        public @NonNull Executor getAsyncNotedExecutor() {
+            return new HandlerExecutor(Handler.getMain());
+        }
+
+        /**
+         * Called when an app-op was noted for this package inside of a two-way binder-call.
+         *
+         * <p>Called on the calling thread just after executing the binder-call. This allows
+         * the app to e.g. collect stack traces to figure out where the access came from.
+         *
+         * @param op The op noted
+         */
+        public abstract void onNoted(@NonNull SyncNotedAppOp op);
+
+        /**
+         * Called when this app noted an app-op for its own package.
+         *
+         * <p>Called on the thread the noted the op. This allows the app to e.g. collect stack
+         * traces to figure out where the access came from.
+         *
+         * @param op The op noted
+         */
+        public abstract void onSelfNoted(@NonNull SyncNotedAppOp op);
+
+        /**
+         * Called when an app-op was noted for this package which cannot be delivered via the other
+         * two mechanisms.
+         *
+         * <p>Called as soon as possible after the app-op was noted, but the delivery delay is not
+         * guaranteed. Due to how async calls work in Android this might even be delivered slightly
+         * before the private data is delivered to the app.
+         *
+         * <p>If the app is not running or no {@link AppOpsCollector} is registered a small amount
+         * of noted app-ops are buffered and then delivered as soon as a collector is registered.
+         *
+         * @param asyncOp The op noted
+         */
+        public abstract void onAsyncNoted(@NonNull AsyncNotedAppOp asyncOp);
+    }
+
+    /**
+     * Generate a stack trace used for noted app-ops logging.
+     *
+     * <p>This strips away the first few and last few stack trace elements as they are not
+     * interesting to apps.
+     */
+    private static String getFormattedStackTrace() {
+        StackTraceElement[] trace = new Exception().getStackTrace();
+
+        int firstInteresting = 0;
+        for (int i = 0; i < trace.length; i++) {
+            if (trace[i].getClassName().startsWith(AppOpsManager.class.getName())
+                    || trace[i].getClassName().startsWith(Parcel.class.getName())
+                    || trace[i].getClassName().contains("$Stub$Proxy")
+                    || trace[i].getClassName().startsWith(DatabaseUtils.class.getName())
+                    || trace[i].getClassName().startsWith("android.content.ContentProviderProxy")
+                    || trace[i].getClassName().startsWith(ContentResolver.class.getName())) {
+                firstInteresting = i;
+            } else {
+                break;
+            }
+        }
+
+        int lastInteresting = trace.length - 1;
+        for (int i = trace.length - 1; i >= 0; i--) {
+            if (trace[i].getClassName().startsWith(HandlerThread.class.getName())
+                    || trace[i].getClassName().startsWith(Handler.class.getName())
+                    || trace[i].getClassName().startsWith(Looper.class.getName())
+                    || trace[i].getClassName().startsWith(Binder.class.getName())
+                    || trace[i].getClassName().startsWith(RuntimeInit.class.getName())
+                    || trace[i].getClassName().startsWith(ZygoteInit.class.getName())
+                    || trace[i].getClassName().startsWith(ActivityThread.class.getName())
+                    || trace[i].getClassName().startsWith(Method.class.getName())
+                    || trace[i].getClassName().startsWith("com.android.server.SystemServer")) {
+                lastInteresting = i;
+            } else {
+                break;
+            }
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = firstInteresting; i <= lastInteresting; i++) {
+            sb.append(trace[i]);
+            if (i != lastInteresting) {
+                sb.append('\n');
+            }
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * Checks whether the given op for a UID and package is active.
      *
      * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
@@ -5504,7 +6135,7 @@
      * @see #startWatchingActive(int[], OnOpActiveChangedListener)
      * @see #stopWatchingMode(OnOpChangedListener)
      * @see #finishOp(int)
-     * @see #startOp(int)
+     * @see #startOp(int, int, String, boolean, String)
      *
      * @hide */
     @TestApi
diff --git a/core/java/android/view/Transaction.aidl b/core/java/android/app/AsyncNotedAppOp.aidl
similarity index 90%
rename from core/java/android/view/Transaction.aidl
rename to core/java/android/app/AsyncNotedAppOp.aidl
index 8d24b43..ebfefd0 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/core/java/android/app/AsyncNotedAppOp.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+package android.app;
 
-parcelable Transaction;
+parcelable AsyncNotedAppOp;
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
new file mode 100644
index 0000000..df6533a
--- /dev/null
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.DataClass;
+
+/**
+ * When an {@link AppOpsManager#noteOp(String, int, String, String) app-op is noted} and the
+ * app the app-op is noted for has a {@link AppOpsManager.AppOpsCollector} registered the note-event
+ * needs to be delivered to the collector. Usually this is done via an {@link SyncNotedAppOp}, but
+ * in some cases this is not possible. In this case an {@link AsyncNotedAppOp} is send to the system
+ * server and then forwarded to the {@link AppOpsManager.AppOpsCollector} in the app.
+ */
+@Immutable
+@DataClass(genEqualsHashCode = true,
+        genAidl = true,
+        genHiddenConstructor = true)
+// - We don't expose the opCode, but rather the public name of the op, hence use a non-standard
+//   getter
+@DataClass.Suppress({"getOpCode"})
+public final class AsyncNotedAppOp implements Parcelable {
+    /** Op that was noted */
+    private final @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int mOpCode;
+
+    /** Uid that noted the op */
+    private final @IntRange(from = 0) int mNotingUid;
+
+    /**
+     * Package that noted the op. {@code null} if the package name that noted the op could be not
+     * be determined (e.g. when the op is noted from native code).
+     */
+    private final @Nullable String mNotingPackageName;
+
+    /** Message associated with the noteOp. This message is set by the app noting the op */
+    private final @NonNull String mMessage;
+
+    /** Milliseconds since epoch when the op was noted */
+    private final @IntRange(from = 0) long mTime;
+
+    /**
+     * @return Op that was noted.
+     */
+    public @NonNull String getOp() {
+        return AppOpsManager.opToPublicName(mOpCode);
+    }
+
+
+
+    // Code below generated by codegen v1.0.0.
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    /**
+     * Creates a new AsyncNotedAppOp.
+     *
+     * @param opCode
+     *   Op that was noted
+     * @param notingUid
+     *   Uid that noted the op
+     * @param notingPackageName
+     *   Package that noted the op
+     * @param message
+     *   Message associated with the noteOp. This message is set by the app noting the op
+     * @param time
+     *   Milliseconds since epoch when the op was noted
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public AsyncNotedAppOp(
+            @IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode,
+            @IntRange(from = 0) int notingUid,
+            @Nullable String notingPackageName,
+            @NonNull String message,
+            @IntRange(from = 0) long time) {
+        this.mOpCode = opCode;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mOpCode,
+                "from", 0,
+                "to", AppOpsManager._NUM_OP - 1);
+        this.mNotingUid = notingUid;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mNotingUid,
+                "from", 0);
+        this.mNotingPackageName = notingPackageName;
+        this.mMessage = message;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMessage);
+        this.mTime = time;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mTime,
+                "from", 0);
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * Uid that noted the op
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) int getNotingUid() {
+        return mNotingUid;
+    }
+
+    /**
+     * Package that noted the op
+     */
+    @DataClass.Generated.Member
+    public @Nullable String getNotingPackageName() {
+        return mNotingPackageName;
+    }
+
+    /**
+     * Message associated with the noteOp. This message is set by the app noting the op
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Milliseconds since epoch when the op was noted
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) long getTime() {
+        return mTime;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        AsyncNotedAppOp that = (AsyncNotedAppOp) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mOpCode == that.mOpCode
+                && mNotingUid == that.mNotingUid
+                && java.util.Objects.equals(mNotingPackageName, that.mNotingPackageName)
+                && java.util.Objects.equals(mMessage, that.mMessage)
+                && mTime == that.mTime;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mOpCode;
+        _hash = 31 * _hash + mNotingUid;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mNotingPackageName);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mMessage);
+        _hash = 31 * _hash + Long.hashCode(mTime);
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mNotingPackageName != null) flg |= 0x4;
+        dest.writeByte(flg);
+        dest.writeInt(mOpCode);
+        dest.writeInt(mNotingUid);
+        if (mNotingPackageName != null) dest.writeString(mNotingPackageName);
+        dest.writeString(mMessage);
+        dest.writeLong(mTime);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
+            = new Parcelable.Creator<AsyncNotedAppOp>() {
+        @Override
+        public AsyncNotedAppOp[] newArray(int size) {
+            return new AsyncNotedAppOp[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            int opCode = in.readInt();
+            int notingUid = in.readInt();
+            String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+            String message = in.readString();
+            long time = in.readLong();
+            return new AsyncNotedAppOp(
+                    opCode,
+                    notingUid,
+                    notingPackageName,
+                    message,
+                    time);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1566503083973L,
+            codegenVersion = "1.0.0",
+            sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index bce243c..905f475 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
 import android.app.SharedElementCallback.OnSharedElementsReadyListener;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -66,13 +67,16 @@
     private final boolean mIsCrossTask;
     private Drawable mReplacedBackground;
     private ArrayList<String> mPendingExitNames;
+    private Runnable mOnTransitionComplete;
 
-    public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
-            ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask) {
+    EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
+            ArrayList<String> sharedElementNames, boolean isReturning, boolean isCrossTask,
+            @NonNull Runnable onTransitionComplete) {
         super(activity.getWindow(), sharedElementNames,
                 getListener(activity, isReturning && !isCrossTask), isReturning);
         mActivity = activity;
         mIsCrossTask = isCrossTask;
+        mOnTransitionComplete = onTransitionComplete;
         setResultReceiver(resultReceiver);
         prepareEnter();
         Bundle resultReceiverBundle = new Bundle();
@@ -578,6 +582,10 @@
                 window.setBackgroundDrawable(null);
             }
         }
+        if (mOnTransitionComplete != null) {
+            mOnTransitionComplete.run();
+            mOnTransitionComplete = null;
+        }
     }
 
     private void sharedElementTransitionStarted() {
@@ -672,6 +680,10 @@
             mBackgroundAnimator.cancel();
             mBackgroundAnimator = null;
         }
+        if (mOnTransitionComplete != null) {
+            mOnTransitionComplete.run();
+            mOnTransitionComplete = null;
+        }
         super.clearState();
     }
 
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 48ca716..dca00a2 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -287,7 +287,8 @@
     void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask,
             in StrictMode.ViolationInfo crashInfo);
     boolean isTopActivityImmersive();
-    void crashApplication(int uid, int initialPid, in String packageName, int userId, in String message);
+    void crashApplication(int uid, int initialPid, in String packageName, int userId,
+            in String message, boolean force);
     @UnsupportedAppUsage
     String getProviderMimeType(in Uri uri, int userId);
     // Cause the specified process to dump the specified heap.
@@ -350,11 +351,13 @@
     // Request a heap dump for the system server.
     void requestSystemServerHeapDump();
 
-    // Deprecated - This method is only used by a few internal components and it will soon be
-    // replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+    // Deprecated - This method is only used by a few internal components and it will soon start
+    // using bug report API (which will be restricted to a few, pre-defined apps).
     // No new code should be calling it.
     @UnsupportedAppUsage
     void requestBugReport(int bugreportType);
+    void requestBugReportWithDescription(in @nullable String shareTitle,
+                in @nullable String shareDescription, int bugreportType);
 
     /**
      *  Takes a telephony bug report and notifies the user with the title and description
@@ -369,7 +372,7 @@
     void requestTelephonyBugReport(in String shareTitle, in String shareDescription);
 
     /**
-     *  Deprecated - This method is only used by Wifi, and it will soon be replaced by a proper
+     *  Deprecated - This method is only used by Wifi, and it will soon start using
      *  bug report API.
      *
      *  Takes a minimal bugreport of Wifi-related state.
@@ -381,6 +384,12 @@
      *          parameters cannot be encoding to an UTF-8 charset.
      */
     void requestWifiBugReport(in String shareTitle, in String shareDescription);
+    void requestInteractiveBugReportWithDescription(in String shareTitle,
+            in String shareDescription);
+
+    void requestInteractiveBugReport();
+    void requestFullBugReport();
+    void requestRemoteBugReport();
 
     @UnsupportedAppUsage
     Intent getIntentForIntentSender(in IIntentSender sender);
@@ -397,23 +406,6 @@
     List<ActivityManager.StackInfo> getAllStackInfos();
     @UnsupportedAppUsage
     void moveTaskToStack(int taskId, int stackId, boolean toTop);
-    /**
-     * Resizes the input stack id to the given bounds.
-     *
-     * @param stackId Id of the stack to resize.
-     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     * @param allowResizeInDockedMode True if the resize should be allowed when the docked stack is
-     *                                active.
-     * @param preserveWindows True if the windows of activities contained in the stack should be
-     *                        preserved.
-     * @param animate True if the stack resize should be animated.
-     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
-     *                          default animation duration should be used.
-     * @throws RemoteException
-     */
-    @UnsupportedAppUsage
-    void resizeStack(int stackId, in Rect bounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration);
     void setFocusedStack(int stackId);
     ActivityManager.StackInfo getFocusedStackInfo();
     @UnsupportedAppUsage
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 47b784b..dda3bb5 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -234,21 +234,15 @@
     void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
     void moveTaskToStack(int taskId, int stackId, boolean toTop);
     /**
-     * Resizes the input stack id to the given bounds.
+     * Resizes the input pinned stack to the given bounds with animation.
      *
-     * @param stackId Id of the stack to resize.
+     * @param stackId Id of the pinned stack to resize.
      * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
-     * @param allowResizeInDockedMode True if the resize should be allowed when the docked stack is
-     *                                active.
-     * @param preserveWindows True if the windows of activities contained in the stack should be
-     *                        preserved.
-     * @param animate True if the stack resize should be animated.
      * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
      *                          default animation duration should be used.
      * @throws RemoteException
      */
-    void resizeStack(int stackId, in Rect bounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration);
+    void animateResizePinnedStack(int stackId, in Rect bounds, int animationDuration);
     boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
             boolean animate, in Rect initialBounds, boolean showRecents);
     /**
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 61867ea..750020e 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -178,6 +178,13 @@
      */
     void onSingleTaskDisplayDrawn(int displayId);
 
+    /*
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    void onSingleTaskDisplayEmpty(int displayId);
+
     /**
      * Called when a task is reparented to a stack on a different display.
      *
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 8987b23..f0b3546 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -883,48 +883,6 @@
             }
         }
 
-        // /apex/com.android.art/lib, /vendor/lib, /odm/lib and /product/lib
-        // are added to the native lib search paths of the classloader.
-        // Note that this is done AFTER the classloader is
-        // created by ApplicationLoaders.getDefault().getClassLoader(...). The
-        // reason is because if we have added the paths when creating the classloader
-        // above, the paths are also added to the search path of the linker namespace
-        // 'classloader-namespace', which will allow ALL libs in the paths to apps.
-        // Since only the libs listed in <partition>/etc/public.libraries.txt can be
-        // available to apps, we shouldn't add the paths then.
-        //
-        // However, we need to add the paths to the classloader (Java) though. This
-        // is because when a native lib is requested via System.loadLibrary(), the
-        // classloader first tries to find the requested lib in its own native libs
-        // search paths. If a lib is not found in one of the paths, dlopen() is not
-        // called at all. This can cause a problem that a vendor public native lib
-        // is accessible when directly opened via dlopen(), but inaccesible via
-        // System.loadLibrary(). In order to prevent the problem, we explicitly
-        // add the paths only to the classloader, and not to the native loader
-        // (linker namespace).
-        List<String> extraLibPaths = new ArrayList<>(4);
-        String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
-        if (!defaultSearchPaths.contains("/apex/com.android.art/lib")) {
-            extraLibPaths.add("/apex/com.android.art/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/vendor/lib")) {
-            extraLibPaths.add("/vendor/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/odm/lib")) {
-            extraLibPaths.add("/odm/lib" + abiSuffix);
-        }
-        if (!defaultSearchPaths.contains("/product/lib")) {
-            extraLibPaths.add("/product/lib" + abiSuffix);
-        }
-        if (!extraLibPaths.isEmpty()) {
-            StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
-            try {
-                ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, extraLibPaths);
-            } finally {
-                setThreadPolicy(oldPolicy);
-            }
-        }
-
         if (addedPaths != null && addedPaths.size() > 0) {
             final String add = TextUtils.join(File.pathSeparator, addedPaths);
             ApplicationLoaders.getDefault().addPath(mDefaultClassLoader, add);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ac53118..2f03ed4 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3274,11 +3274,8 @@
      *
      * @param requiresFreeform requires the remoteinput to allow freeform or not.
      * @return the result pair, {@code null} if no result is found.
-     *
-     * @hide
      */
     @Nullable
-    @SystemApi
     public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) {
         if (actions == null) {
             return null;
@@ -3301,11 +3298,9 @@
     }
 
     /**
-     * Returns the actions that are contextual out of the actions in this notification.
-     *
-     * @hide
+     * Returns the actions that are contextual (that is, suggested because of the content of the
+     * notification) out of the actions in this notification.
      */
-    @SystemApi
     public @NonNull List<Notification.Action> getContextualActions() {
         if (actions == null) return Collections.emptyList();
 
@@ -7705,11 +7700,11 @@
             }
 
             /**
-             * @return A list of messages read from the bundles.
-             *
-             * @hide
+             * Returns a list of messages read from the given bundle list, e.g.
+             * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}.
              */
-            public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+            @NonNull
+            public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) {
                 if (bundles == null) {
                     return new ArrayList<>();
                 }
@@ -7726,13 +7721,12 @@
             }
 
             /**
-             * @return The message that is stored in the bundle or null if the message couldn't be
-             * resolved.
-             *
+             * Returns the message that is stored in the bundle (e.g. one of the values in the lists
+             * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the
+             * message couldn't be resolved.
              * @hide
              */
             @Nullable
-            @SystemApi
             public static Message getMessageFromBundle(@NonNull Bundle bundle) {
                 try {
                     if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
@@ -8156,7 +8150,9 @@
                 Action action, StandardTemplateParams p) {
             final boolean tombstone = (action.actionIntent == null);
             container.setViewVisibility(buttonId, View.VISIBLE);
-            container.setImageViewIcon(buttonId, action.getIcon());
+            if (buttonId != R.id.media_seamless) {
+                container.setImageViewIcon(buttonId, action.getIcon());
+            }
 
             // If the action buttons should not be tinted, then just use the default
             // notification color. Otherwise, just use the passed-in color.
@@ -8210,6 +8206,10 @@
                     view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE);
                 }
             }
+            bindMediaActionButton(view, R.id.media_seamless, new Action(
+                    R.drawable.ic_media_seamless, mBuilder.mContext.getString(
+                            com.android.internal.R.string.ext_media_seamless_action), null), p);
+            view.setViewVisibility(R.id.media_seamless, View.GONE);
             handleImage(view);
             // handle the content margin
             int endMargin = R.dimen.notification_content_margin_end;
@@ -8542,24 +8542,34 @@
          * If set and the app creating the bubble is in the foreground, the bubble will be posted
          * in its expanded state, with the contents of {@link #getIntent()} in a floating window.
          *
-         * <p>If the app creating the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
+        public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001;
 
         /**
          * If set and the app posting the bubble is in the foreground, the bubble will
          * be posted <b>without</b> the associated notification in the notification shade.
          *
-         * <p>If the app posting the bubble is not in the foreground this flag has no effect.</p>
+         * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+         * The app is considered foreground if it is visible and on the screen, note that
+         * a foreground service does not qualify.
+         * </p>
          *
          * <p>Generally this flag should only be set if the user has performed an action to request
          * or create a bubble, or if the user has seen the content in the notification and the
          * notification is no longer relevant.</p>
+         *
+         * @hide
          */
-        private static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
+        public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002;
 
         private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent,
                 Icon icon, int height, @DimenRes int heightResId) {
@@ -8675,11 +8685,21 @@
             out.writeInt(mDesiredHeightResId);
         }
 
-        private void setFlags(int flags) {
+        /**
+         * @hide
+         */
+        public void setFlags(int flags) {
             mFlags = flags;
         }
 
         /**
+         * @hide
+         */
+        public int getFlags() {
+            return mFlags;
+        }
+
+        /**
          * Builder to construct a {@link BubbleMetadata} object.
          */
         public static final class Builder {
@@ -8795,6 +8815,8 @@
              * {@link #getIntent()} in a floating window).
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
@@ -8813,6 +8835,8 @@
              * the notification shade.
              *
              * <p>This flag has no effect if the app posting the bubble is not in the foreground.
+             * The app is considered foreground if it is visible and on the screen, note that
+             * a foreground service does not qualify.
              * </p>
              *
              * <p>Generally, this flag should only be set if the user has performed an action to
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 93e4ddc..20d977b 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -710,6 +710,14 @@
     }
 
     /**
+     * Returns whether the user has chosen the sound of this channel.
+     * @see #getSound()
+     */
+    public boolean hasUserSetSound() {
+        return (mUserLockedFields & USER_LOCKED_SOUND) != 0;
+    }
+
+    /**
      * @hide
      */
     public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
new file mode 100644
index 0000000..f7b83d4
--- /dev/null
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.Immutable;
+
+/**
+ * Description of an app-op that was noted for the current process.
+ *
+ * <p>This is either delivered after a
+ * {@link AppOpsManager.AppOpsCollector#onNoted(SyncNotedAppOp) two way binder call} or
+ * when the app
+ * {@link AppOpsManager.AppOpsCollector#onSelfNoted(SyncNotedAppOp) notes an app-op for
+ * itself}.
+ */
+@Immutable
+public final class SyncNotedAppOp {
+    private final int mOpCode;
+
+    /**
+     * @return The op that was noted.
+     */
+    public @NonNull String getOp() {
+        return AppOpsManager.opToPublicName(mOpCode);
+    }
+
+    /**
+     * Create a new sync op description
+     *
+     * @param opCode The op that was noted
+     *
+     * @hide
+     */
+    public SyncNotedAppOp(@IntRange(from = 0, to = AppOpsManager._NUM_OP - 1) int opCode) {
+        mOpCode = opCode;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof SyncNotedAppOp)) {
+            return false;
+        }
+
+        return mOpCode == ((SyncNotedAppOp) other).mOpCode;
+    }
+
+    @Override
+    public int hashCode() {
+        return mOpCode;
+    }
+}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index e3a0e11..46045fa 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -180,6 +180,10 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
     }
 
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index f9b96c5..fd93450 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,7 +16,7 @@
 
 package android.app;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService.Callbacks;
 import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -1246,7 +1246,7 @@
                 }
 
                 @Override
-                public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+                public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
                     /* do nothing */
                     return false;
                 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a136bbd..02cac23 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2531,14 +2531,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;
 
@@ -3186,10 +3185,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.
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 86e768d..f9e710e 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1080,6 +1080,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdPackage() {
             return mIdPackage;
         }
@@ -1089,6 +1090,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdType() {
             return mIdType;
         }
@@ -1098,6 +1100,7 @@
          * identifier.  See {@link android.view.ViewStructure#setId ViewStructure.setId}
          * for more information.
          */
+        @Nullable
         public String getIdEntry() {
             return mIdEntry;
         }
@@ -1391,6 +1394,7 @@
          * For example, a button will report "android.widget.Button" meaning it behaves
          * like a {@link android.widget.Button}.
          */
+        @Nullable
         public String getClassName() {
             return mClassName;
         }
@@ -1399,6 +1403,7 @@
          * Returns any content description associated with the node, which semantically describes
          * its purpose for accessibility and other uses.
          */
+        @Nullable
         public CharSequence getContentDescription() {
             return mContentDescription;
         }
@@ -1473,6 +1478,7 @@
          * Returns any text associated with the node that is displayed to the user, or null
          * if there is none.
          */
+        @Nullable
         public CharSequence getText() {
             return mText != null ? mText.mText : null;
         }
@@ -1560,6 +1566,7 @@
          * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
          * not for autofill purposes.
          */
+        @Nullable
         public int[] getTextLineCharOffsets() {
             return mText != null ? mText.mLineCharOffsets : null;
         }
@@ -1573,6 +1580,7 @@
          * <p>It's only relevant when the {@link AssistStructure} is used for assist purposes,
          * not for autofill purposes.
          */
+        @Nullable
         public int[] getTextLineBaselines() {
             return mText != null ? mText.mLineBaselines : null;
         }
@@ -1592,6 +1600,7 @@
          * Return additional hint text associated with the node; this is typically used with
          * a node that takes user input, describing to the user what the input means.
          */
+        @Nullable
         public String getHint() {
             return mText != null ? mText.mHint : null;
         }
@@ -1610,6 +1619,7 @@
         /**
          * Return a Bundle containing optional vendor-specific extension information.
          */
+        @Nullable
         public Bundle getExtras() {
             return mExtras;
         }
diff --git a/core/java/android/app/role/RoleControllerService.java b/core/java/android/app/role/RoleControllerService.java
index 85db6a4..06623f9 100644
--- a/core/java/android/app/role/RoleControllerService.java
+++ b/core/java/android/app/role/RoleControllerService.java
@@ -279,6 +279,10 @@
     /**
      * Check whether an application is visible for a role.
      *
+     * While an application can be qualified for a role, it can still stay hidden from user (thus
+     * not visible). If an application is visible for a role, we may show things related to the role
+     * for it, e.g. showing an entry pointing to the role settings in its application info page.
+     *
      * @param roleName name of the role to check for
      * @param packageName package name of the application to check for
      *
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 84c6855..5dbca12 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -276,10 +276,24 @@
         public static final int DEVICE_STARTUP = 27;
 
         /**
+         * An event type denoting that a user has been unlocked for the first time. This event
+         * mainly indicates when the user's credential encrypted storage was first accessible.
+         * @hide
+         */
+        public static final int USER_UNLOCKED = 28;
+
+        /**
+         * An event type denoting that a user has been stopped. This typically happens when the
+         * system is being turned off or when users are being switched.
+         * @hide
+         */
+        public static final int USER_STOPPED = 29;
+
+        /**
          * Keep in sync with the greatest event type value.
          * @hide
          */
-        public static final int MAX_EVENT_TYPE = 27;
+        public static final int MAX_EVENT_TYPE = 29;
 
         /** @hide */
         public static final int FLAG_IS_PACKAGE_INSTANT_APP = 1 << 0;
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 749a011..656f474 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -31,6 +31,7 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.ArrayMap;
 
 import java.lang.annotation.Retention;
@@ -290,6 +291,9 @@
      * </p>
      *
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -324,6 +328,9 @@
      * the specified interval. The results are ordered as in
      * {@link #queryUsageStats(int, long, long)}.
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -362,6 +369,9 @@
      * </ul>
      *
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
@@ -395,6 +405,9 @@
      * Query for events in the given time range. Events are only kept by the system for a few
      * days.
      * <p> The caller must have {@link android.Manifest.permission#PACKAGE_USAGE_STATS} </p>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
      *                 Defined in terms of "Unix time", see
@@ -418,6 +431,9 @@
 
     /**
      * Like {@link #queryEvents(long, long)}, but only returns events for the calling package.
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then {@code null} will be returned.</em>
      *
      * @param beginTime The inclusive beginning of the range of events to include in the results.
      *                 Defined in terms of "Unix time", see
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index bbec6b3..b3260c4 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.res.Configuration;
 import android.os.UserHandle;
+import android.os.UserManager;
 
 import java.util.List;
 import java.util.Set;
@@ -33,7 +34,10 @@
 public abstract class UsageStatsManagerInternal {
 
     /**
-     * Reports an event to the UsageStatsManager.
+     * Reports an event to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param component The component for which this event occurred.
      * @param userId The user id to which the component belongs to.
@@ -48,7 +52,10 @@
             int instanceId, ComponentName taskRoot);
 
     /**
-     * Reports an event to the UsageStatsManager.
+     * Reports an event to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package for which this event occurred.
      * @param userId The user id to which the component belongs to.
@@ -58,14 +65,20 @@
     public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);
 
     /**
-     * Reports a configuration change to the UsageStatsManager.
+     * Reports a configuration change to the UsageStatsManager. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param config The new device configuration.
      */
     public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);
 
     /**
-     * Reports that an application has posted an interruptive notification.
+     * Reports that an application has posted an interruptive notification. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package name of the app that posted the notification
      * @param channelId The ID of the NotificationChannel to which the notification was posted
@@ -75,7 +88,10 @@
             @UserIdInt int userId);
 
     /**
-     * Reports that an action equivalent to a ShortcutInfo is taken by the user.
+     * Reports that an action equivalent to a ShortcutInfo is taken by the user. <br/>
+     * <em>Note: Starting from {@link android.os.Build.VERSION_CODES#R Android R}, if the user's
+     * device is not in an unlocked state (as defined by {@link UserManager#isUserUnlocked()}),
+     * then this event will be added to a queue and processed once the device is unlocked.</em>
      *
      * @param packageName The package name of the shortcut publisher
      * @param shortcutId The ID of the shortcut in question
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index f003d4b..c20cb25 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -56,6 +56,7 @@
     static final int HANDLE_PROVIDERS_CHANGED = 3;
     @UnsupportedAppUsage
     static final int HANDLE_VIEW_DATA_CHANGED = 4;
+    static final int HANDLE_APP_WIDGET_REMOVED = 5;
 
     final static Object sServiceLock = new Object();
     @UnsupportedAppUsage
@@ -103,6 +104,14 @@
             msg.sendToTarget();
         }
 
+        public void appWidgetRemoved(int appWidgetId) {
+            Handler handler = mWeakHandler.get();
+            if (handler == null) {
+                return;
+            }
+            handler.obtainMessage(HANDLE_APP_WIDGET_REMOVED, appWidgetId, 0).sendToTarget();
+        }
+
         public void providersChanged() {
             Handler handler = mWeakHandler.get();
             if (handler == null) {
@@ -137,6 +146,10 @@
                     updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
                     break;
                 }
+                case HANDLE_APP_WIDGET_REMOVED: {
+                    dispatchOnAppWidgetRemoved(msg.arg1);
+                    break;
+                }
                 case HANDLE_PROVIDER_CHANGED: {
                     onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
                     break;
@@ -224,6 +237,10 @@
                     break;
                 case PendingHostUpdate.TYPE_VIEW_DATA_CHANGED:
                     viewDataChanged(update.appWidgetId, update.viewId);
+                    break;
+                case PendingHostUpdate.TYPE_APP_WIDGET_REMOVED:
+                    dispatchOnAppWidgetRemoved(update.appWidgetId);
+                    break;
             }
         }
     }
@@ -426,6 +443,21 @@
         }
     }
 
+    void dispatchOnAppWidgetRemoved(int appWidgetId) {
+        synchronized (mViews) {
+            mViews.remove(appWidgetId);
+        }
+        onAppWidgetRemoved(appWidgetId);
+    }
+
+    /**
+     * Called when the app widget is removed for appWidgetId
+     * @param appWidgetId
+     */
+    public void onAppWidgetRemoved(int appWidgetId) {
+        // Does nothing
+    }
+
     /**
      * Called when the set of available widgets changes (ie. widget containing packages
      * are added, updated or removed, or widget components are enabled or disabled.)
diff --git a/core/java/android/appwidget/PendingHostUpdate.java b/core/java/android/appwidget/PendingHostUpdate.java
index e1bf159..1f1d644 100644
--- a/core/java/android/appwidget/PendingHostUpdate.java
+++ b/core/java/android/appwidget/PendingHostUpdate.java
@@ -28,6 +28,7 @@
     static final int TYPE_VIEWS_UPDATE = 0;
     static final int TYPE_PROVIDER_CHANGED = 1;
     static final int TYPE_VIEW_DATA_CHANGED = 2;
+    static final int TYPE_APP_WIDGET_REMOVED = 3;
 
     final int appWidgetId;
     final int type;
@@ -53,6 +54,13 @@
         return update;
     }
 
+    /**
+     * IAppWidgetHost appWidgetRemoved implimentaion
+     */
+    public static PendingHostUpdate appWidgetRemoved(int appWidgetId) {
+        return new PendingHostUpdate(appWidgetId, TYPE_APP_WIDGET_REMOVED);
+    }
+
     private PendingHostUpdate(int appWidgetId, int type) {
         this.appWidgetId = appWidgetId;
         this.type = type;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 39d63de..e7ba85a 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -858,7 +858,10 @@
         if (DBG) {
             Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state));
         }
-        return (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON);
+        return (state == BluetoothAdapter.STATE_ON
+                || state == BluetoothAdapter.STATE_BLE_ON
+                || state == BluetoothAdapter.STATE_TURNING_ON
+                || state == BluetoothAdapter.STATE_TURNING_OFF);
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java
index dfec4e1..a0aa2de 100644
--- a/core/java/android/bluetooth/BluetoothOutputStream.java
+++ b/core/java/android/bluetooth/BluetoothOutputStream.java
@@ -75,16 +75,4 @@
         }
         mSocket.write(b, offset, count);
     }
-
-    /**
-     * Wait until the data in sending queue is emptied. A polling version
-     * for flush implementation. Use it to ensure the writing data afterwards will
-     * be packed in the new RFCOMM frame.
-     *
-     * @throws IOException if an i/o error occurs.
-     * @since Android 4.2.3
-     */
-    public void flush() throws IOException {
-        mSocket.flush();
-    }
 }
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index a6e3153..760166b 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -515,20 +515,6 @@
         return mSocketIS.available();
     }
 
-    /**
-     * Wait until the data in sending queue is emptied. A polling version
-     * for flush implementation. Used to ensure the writing data afterwards will
-     * be packed in new RFCOMM frame.
-     *
-     * @throws IOException if an i/o error occurs.
-     */
-    @UnsupportedAppUsage
-    /*package*/ void flush() throws IOException {
-        if (mSocketOS == null) throw new IOException("flush is called on null OutputStream");
-        if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
-        mSocketOS.flush();
-    }
-
     /*package*/ int read(byte[] b, int offset, int length) throws IOException {
         int ret = 0;
         if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java
index 082663e..97b33b7 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -97,6 +97,8 @@
 
     /**
      * Returns if autofill is disabled by service to the given activity.
+     *
+     * @hide
      */
     public boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
         final long elapsedTime = SystemClock.elapsedRealtime();
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2a17800..3418b7b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5413,8 +5413,8 @@
             "android.intent.extra.ALLOW_MULTIPLE";
 
     /**
-     * The integer userHandle carried with broadcast intents related to addition, removal and
-     * switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+     * The integer userHandle (i.e. userId) carried with broadcast intents related to addition,
+     * removal and switching of users and managed profiles - {@link #ACTION_USER_ADDED},
      * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
      *
      * @hide
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 9d52363..099dea2 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -60,9 +60,7 @@
  * multiple possible matching values (via {@link #addAction},
  * {@link #addDataType}, {@link #addDataScheme}, {@link #addDataSchemeSpecificPart},
  * {@link #addDataAuthority}, {@link #addDataPath}, and {@link #addCategory}, respectively).
- * For actions, the field
- * will not be tested if no values have been given (treating it as a wildcard);
- * if no data characteristics are specified, however, then the filter will
+ * For actions, if no data characteristics are specified, then the filter will
  * only match intents that contain no data.
  *
  * <p>The data characteristic is
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 6fe6e99..e24512a 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -16,6 +16,8 @@
 
 package android.content;
 
+import static android.app.AppOpsManager.strOpToOp;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -75,6 +77,16 @@
     }
 
     /**
+     * @deprecated Use {@link #checkPermission(Context, String, int, int, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkPermission(@NonNull Context context, @NonNull String permission,
+            int pid, int uid, @Nullable String packageName) {
+        return checkPermission(context, permission, pid, uid, packageName, null);
+    }
+
+    /**
      * Checks whether a given package in a UID and PID has a given permission
      * and whether the app op that corresponds to this permission is allowed.
      *
@@ -84,12 +96,14 @@
      * @param uid The uid for which to check.
      * @param packageName The package name for which to check. If null the
      *     the first package for the calling UID will be used.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkPermission(@NonNull Context context, @NonNull String permission,
-            int pid, int uid, @Nullable String packageName) {
+            int pid, int uid, @Nullable String packageName, @Nullable String message) {
         if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
             return PERMISSION_DENIED;
         }
@@ -108,7 +122,8 @@
             packageName = packageNames[0];
         }
 
-        if (appOpsManager.noteProxyOpNoThrow(op, packageName, uid) != AppOpsManager.MODE_ALLOWED) {
+        if (appOpsManager.noteProxyOpNoThrow(strOpToOp(op), packageName, uid, message)
+                != AppOpsManager.MODE_ALLOWED) {
             return PERMISSION_DENIED_APP_OP;
         }
 
@@ -131,7 +146,17 @@
     public static int checkSelfPermission(@NonNull Context context,
             @NonNull String permission) {
         return checkPermission(context, permission, Process.myPid(),
-                Process.myUid(), context.getPackageName());
+                Process.myUid(), context.getPackageName(), null /* self access */);
+    }
+
+    /**
+     * @deprecated Use {@link #checkCallingPermission(Context, String, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkCallingPermission(@NonNull Context context,
+            @NonNull String permission, @Nullable String packageName) {
+        return checkCallingPermission(context, permission, packageName, null);
     }
 
     /**
@@ -142,17 +167,29 @@
      * @param permission The permission to check.
      * @param packageName The package name making the IPC. If null the
      *     the first package for the calling UID will be used.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkCallingPermission(@NonNull Context context,
-            @NonNull String permission, @Nullable String packageName) {
+            @NonNull String permission, @Nullable String packageName, @Nullable String message) {
         if (Binder.getCallingPid() == Process.myPid()) {
             return PERMISSION_DENIED;
         }
         return checkPermission(context, permission, Binder.getCallingPid(),
-                Binder.getCallingUid(), packageName);
+                Binder.getCallingUid(), packageName, message);
+    }
+
+    /**
+     * @deprecated Use {@link #checkCallingOrSelfPermission(Context, String, String)} instead
+     */
+    @Deprecated
+    @PermissionResult
+    public static int checkCallingOrSelfPermission(@NonNull Context context,
+            @NonNull String permission) {
+        return checkCallingOrSelfPermission(context, permission, null);
     }
 
     /**
@@ -161,15 +198,17 @@
      *
      * @param context Context for accessing resources.
      * @param permission The permission to check.
+     * @param message A message describing the reason the permission was checked
+     *
      * @return The permission check result which is either {@link #PERMISSION_GRANTED}
      *     or {@link #PERMISSION_DENIED} or {@link #PERMISSION_DENIED_APP_OP}.
      */
     @PermissionResult
     public static int checkCallingOrSelfPermission(@NonNull Context context,
-            @NonNull String permission) {
+            @NonNull String permission, @Nullable String message) {
         String packageName = (Binder.getCallingPid() == Process.myPid())
                 ? context.getPackageName() : null;
         return checkPermission(context, permission, Binder.getCallingPid(),
-                Binder.getCallingUid(), packageName);
+                Binder.getCallingUid(), packageName, message);
     }
 }
diff --git a/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java b/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
new file mode 100644
index 0000000..1a720d5
--- /dev/null
+++ b/core/java/android/content/pm/AndroidTelephonyCommonUpdater.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import static android.content.pm.SharedLibraryNames.ANDROID_TELEPHONY_COMMON;
+
+
+import com.android.internal.compat.IPlatformCompat;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.pm.PackageParser.Package;
+
+import android.os.Build.VERSION_CODES;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Updates a package to ensure that
+ * <ul>
+ * <li> if apps have target SDK < R, then telephony-common library is included by default to
+ * their class path. Even without <uses-library>.</li>
+ * <li> if apps with target SDK level >= R && have special permission (or Phone UID):
+ * apply <uses-library> on telephony-common should work.</li>
+ * <li> Otherwise not allow to use the lib.
+ * See {@link PackageSharedLibraryUpdater#removeLibrary(Package, String)}.</li>
+ * </ul>
+ *
+ * @hide
+ */
+@VisibleForTesting
+public class AndroidTelephonyCommonUpdater extends PackageSharedLibraryUpdater {
+
+    private static final String TAG = AndroidTelephonyCommonUpdater.class.getSimpleName();
+    /**
+     * Restrict telephony-common lib for apps having target SDK >= R
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = VERSION_CODES.Q)
+    static final long RESTRICT_TELEPHONY_COMMON_CHANGE_ID = 139318877L;
+
+    private static boolean apkTargetsApiLevelLessThanROrCurrent(Package pkg) {
+        boolean shouldRestrict = false;
+        try {
+            IBinder b = ServiceManager.getService("platform_compat");
+            IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(b);
+            shouldRestrict = platformCompat.isChangeEnabled(RESTRICT_TELEPHONY_COMMON_CHANGE_ID,
+                pkg.applicationInfo);
+        } catch (RemoteException ex) {
+            Log.e(TAG, ex.getMessage());
+        }
+        // TODO(b/139318877): remove version check for CUR_DEVELOPEMENT after clean up work.
+        return !shouldRestrict
+            || pkg.applicationInfo.targetSdkVersion == VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
+    @Override
+    public void updatePackage(Package pkg) {
+        // for apps with targetSDKVersion < R include the library for backward compatibility.
+        if (apkTargetsApiLevelLessThanROrCurrent(pkg)) {
+            prefixRequiredLibrary(pkg, ANDROID_TELEPHONY_COMMON);
+        } else if (pkg.mSharedUserId == null || !pkg.mSharedUserId.equals("android.uid.phone")) {
+            // if apps target >= R
+            removeLibrary(pkg, ANDROID_TELEPHONY_COMMON);
+        }
+    }
+}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 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 4331bd4..797ba64b 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -51,6 +51,8 @@
 
         packageUpdaters.add(new AndroidHidlUpdater());
 
+        packageUpdaters.add(new AndroidTelephonyCommonUpdater());
+
         // Add this before adding AndroidTestBaseUpdater so that android.test.base comes before
         // android.test.mock.
         packageUpdaters.add(new AndroidTestRunnerSplitUpdater());
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c561013..8dfe00a 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;
@@ -3379,7 +3379,7 @@
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    @Disabled
     public static final long FILTER_APPLICATION_QUERY = 135549675L;
 
     /** {@hide} */
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index de61d70..24ee213 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.AppIdInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -321,25 +322,24 @@
             Bundle verificationBundle, int userId);
 
     /**
-     * Grants access to the package metadata for an ephemeral application.
+     * Grants implicit access based on an interaction between two apps. This grants the target app
+     * access to the calling application's package metadata.
      * <p>
-     * When an ephemeral application explicitly tries to interact with a full
-     * install application [via an activity, service or provider that has been
-     * exposed using the {@code visibleToInstantApp} attribute], the normal
-     * application must be able to see metadata about the connecting ephemeral
-     * app. If the ephemeral application uses an implicit intent [ie action VIEW,
-     * category BROWSABLE], it remains hidden from the launched activity.
+     * When an application explicitly tries to interact with another application [via an
+     * activity, service or provider that is either declared in the caller's
+     * manifest via the {@code <queries>} tag or has been exposed via the target apps manifest using
+     * the {@code visibleToInstantApp} attribute], the target application must be able to see
+     * metadata about the calling app. If the calling application uses an implicit intent [ie
+     * action VIEW, category BROWSABLE], it remains hidden from the launched app.
      * <p>
-     * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
-     * is not for a fully installed app, this method will be a no-op.
-     *
      * @param userId the user
      * @param intent the intent that triggered the grant
-     * @param targetAppId The app ID of the fully installed application
-     * @param ephemeralAppId The app ID of the ephemeral application
+     * @param callingAppId The app ID 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, @AppIdInt int callingAppId,
+            @AppIdInt int targetAppId);
 
     public abstract boolean isInstantAppInstallerComponent(ComponentName component);
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index ef08bf5..0b157fa 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -57,6 +57,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageParserCacheHelper.ReadHelper;
 import android.content.pm.PackageParserCacheHelper.WriteHelper;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
@@ -79,7 +80,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
-import android.permission.PermissionManager;
 import android.system.ErrnoException;
 import android.system.OsConstants;
 import android.system.StructStat;
@@ -2528,11 +2528,17 @@
             Slog.i(TAG, newPermsMsg.toString());
         }
 
+        List<SplitPermissionInfoParcelable> splitPermissions;
 
-        final int NS = PermissionManager.SPLIT_PERMISSIONS.size();
-        for (int is=0; is<NS; is++) {
-            final PermissionManager.SplitPermissionInfo spi =
-                    PermissionManager.SPLIT_PERMISSIONS.get(is);
+        try {
+            splitPermissions = ActivityThread.getPermissionManager().getSplitPermissions();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        final int listSize = splitPermissions.size();
+        for (int is = 0; is < listSize; is++) {
+            final SplitPermissionInfoParcelable spi = splitPermissions.get(is);
             if (pkg.applicationInfo.targetSdkVersion >= spi.getTargetSdk()
                     || !pkg.requestedPermissions.contains(spi.getSplitPermission())) {
                 continue;
@@ -4064,32 +4070,54 @@
                         intentInfo, outError)) {
                     return false;
                 }
-                Intent intent = new Intent();
-                if (intentInfo.countActions() != 1) {
-                    outError[0] = "intent tags must contain exactly one action.";
-                    return false;
-                }
-                intent.setAction(intentInfo.getAction(0));
-                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
-                    intent.addCategory(intentInfo.getCategory(i));
-                }
+
                 Uri data = null;
                 String dataType = null;
-                if (intentInfo.countDataTypes() > 1) {
+                String host = "";
+                final int numActions = intentInfo.countActions();
+                final int numSchemes = intentInfo.countDataSchemes();
+                final int numTypes = intentInfo.countDataTypes();
+                final int numHosts = intentInfo.getHosts().length;
+                if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
+                    outError[0] = "intent tags must contain either an action or data.";
+                    return false;
+                }
+                if (numActions > 1) {
+                    outError[0] = "intent tag may have at most one action.";
+                    return false;
+                }
+                if (numTypes > 1) {
                     outError[0] = "intent tag may have at most one data type.";
                     return false;
                 }
-                if (intentInfo.countDataSchemes() > 1) {
+                if (numSchemes > 1) {
                     outError[0] = "intent tag may have at most one data scheme.";
                     return false;
                 }
-                if (intentInfo.countDataTypes() == 1) {
-                    data = Uri.fromParts(intentInfo.getDataType(0), "", null);
+                if (numHosts > 1) {
+                    outError[0] = "intent tag may have at most one data host.";
+                    return false;
                 }
-                if (intentInfo.countDataSchemes() == 1) {
-                    dataType = intentInfo.getDataScheme(0);
+                Intent intent = new Intent();
+                for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
+                    intent.addCategory(intentInfo.getCategory(i));
+                }
+                if (numHosts == 1) {
+                    host = intentInfo.getHosts()[0];
+                }
+                if (numSchemes == 1) {
+                    data = new Uri.Builder()
+                            .scheme(intentInfo.getDataScheme(0))
+                            .authority(host)
+                            .build();
+                }
+                if (numTypes == 1) {
+                    dataType = intentInfo.getDataType(0);
                 }
                 intent.setDataAndType(data, dataType);
+                if (numActions == 1) {
+                    intent.setAction(intentInfo.getAction(0));
+                }
                 owner.mQueriesIntents = ArrayUtils.add(owner.mQueriesIntents, intent);
             } else if (parser.getName().equals("package")) {
                 final TypedArray sa = res.obtainAttributes(parser,
@@ -8501,5 +8529,4 @@
             this.error = error;
         }
     }
-
 }
diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java
index a607a9f..4c66fc0 100644
--- a/core/java/android/content/pm/SharedLibraryNames.java
+++ b/core/java/android/content/pm/SharedLibraryNames.java
@@ -33,4 +33,6 @@
     static final String ANDROID_TEST_RUNNER = "android.test.runner";
 
     public static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy";
+
+    public static final String ANDROID_TELEPHONY_COMMON = "telephony-common";
 }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d03bea2..e65d761 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -57,7 +57,7 @@
 
     /**
      * Primary user. Only one user can have this flag set. It identifies the first human user
-     * on a device.
+     * on a device. This flag is not supported in headless system user mode.
      */
     @UnsupportedAppUsage
     public static final int FLAG_PRIMARY = 0x00000001;
diff --git a/core/java/android/view/Transaction.aidl b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl
similarity index 72%
copy from core/java/android/view/Transaction.aidl
copy to core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl
index 8d24b43..d84454c 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2019 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.
  * You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+ package android.content.pm.permission;
 
-parcelable Transaction;
+ parcelable SplitPermissionInfoParcelable;
\ No newline at end of file
diff --git a/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
new file mode 100644
index 0000000..421ae49
--- /dev/null
+++ b/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.permission;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Parcelable version of {@link android.permission.PermissionManager.SplitPermissionInfo}
+ * @hide
+ */
+@DataClass(genEqualsHashCode = true)
+public class SplitPermissionInfoParcelable implements Parcelable {
+
+    /**
+     * The permission that is split.
+     */
+    @NonNull
+    private final String mSplitPermission;
+
+    /**
+     * The permissions that are added.
+     */
+    @NonNull
+    private final List<String> mNewPermissions;
+
+    /**
+     * The target API level when the permission was split.
+     */
+    @IntRange(from = 0)
+    private final int mTargetSdk;
+
+    private void onConstructed() {
+        Preconditions.checkCollectionElementsNotNull(mNewPermissions, "newPermissions");
+    }
+
+
+
+    // Code below generated by codegen v1.0.0.
+    //
+    // DO NOT MODIFY!
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java
+    //
+    // CHECKSTYLE:OFF Generated code
+
+    /**
+     * Creates a new SplitPermissionInfoParcelable.
+     *
+     * @param splitPermission
+     *   The permission that is split.
+     * @param newPermissions
+     *   The permissions that are added.
+     * @param targetSdk
+     *   The target API level when the permission was split.
+     */
+    @DataClass.Generated.Member
+    public SplitPermissionInfoParcelable(
+            @NonNull String splitPermission,
+            @NonNull List<String> newPermissions,
+            @IntRange(from = 0) int targetSdk) {
+        this.mSplitPermission = splitPermission;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mSplitPermission);
+        this.mNewPermissions = newPermissions;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mNewPermissions);
+        this.mTargetSdk = targetSdk;
+        com.android.internal.util.AnnotationValidations.validate(
+                IntRange.class, null, mTargetSdk,
+                "from", 0);
+
+        onConstructed();
+    }
+
+    /**
+     * The permission that is split.
+     */
+    @DataClass.Generated.Member
+    public @NonNull String getSplitPermission() {
+        return mSplitPermission;
+    }
+
+    /**
+     * The permissions that are added.
+     */
+    @DataClass.Generated.Member
+    public @NonNull List<String> getNewPermissions() {
+        return mNewPermissions;
+    }
+
+    /**
+     * The target API level when the permission was split.
+     */
+    @DataClass.Generated.Member
+    public @IntRange(from = 0) int getTargetSdk() {
+        return mTargetSdk;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(SplitPermissionInfoParcelable other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        SplitPermissionInfoParcelable that = (SplitPermissionInfoParcelable) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && Objects.equals(mSplitPermission, that.mSplitPermission)
+                && Objects.equals(mNewPermissions, that.mNewPermissions)
+                && mTargetSdk == that.mTargetSdk;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + Objects.hashCode(mSplitPermission);
+        _hash = 31 * _hash + Objects.hashCode(mNewPermissions);
+        _hash = 31 * _hash + mTargetSdk;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        dest.writeString(mSplitPermission);
+        dest.writeStringList(mNewPermissions);
+        dest.writeInt(mTargetSdk);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<SplitPermissionInfoParcelable> CREATOR
+            = new Parcelable.Creator<SplitPermissionInfoParcelable>() {
+        @Override
+        public SplitPermissionInfoParcelable[] newArray(int size) {
+            return new SplitPermissionInfoParcelable[size];
+        }
+
+        @Override
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        public SplitPermissionInfoParcelable createFromParcel(Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            String splitPermission = in.readString();
+            List<String> newPermissions = new java.util.ArrayList<>();
+            in.readStringList(newPermissions);
+            int targetSdk = in.readInt();
+            return new SplitPermissionInfoParcelable(
+                    splitPermission,
+                    newPermissions,
+                    targetSdk);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1567634390477L,
+            codegenVersion = "1.0.0",
+            sourceFile = "frameworks/base/core/java/android/content/pm/permission/SplitPermissionInfoParcelable.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mSplitPermission\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mNewPermissions\nprivate final @android.annotation.IntRange(from=0L) int mTargetSdk\nprivate  void onConstructed()\nclass SplitPermissionInfoParcelable extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 861ae7b..ac1cbd4 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -23,6 +23,7 @@
 import static android.content.ConfigurationProto.KEYBOARD;
 import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
 import static android.content.ConfigurationProto.LOCALES;
+import static android.content.ConfigurationProto.LOCALE_LIST;
 import static android.content.ConfigurationProto.MCC;
 import static android.content.ConfigurationProto.MNC;
 import static android.content.ConfigurationProto.NAVIGATION;
@@ -1111,7 +1112,7 @@
             protoOutputStream.write(MCC, mcc);
             protoOutputStream.write(MNC, mnc);
             if (mLocaleList != null) {
-                mLocaleList.writeToProto(protoOutputStream, LOCALES);
+                protoOutputStream.write(LOCALE_LIST, mLocaleList.toLanguageTags());
             }
             protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
             protoOutputStream.write(COLOR_MODE, colorMode);
@@ -1222,7 +1223,15 @@
                                                         .setVariant(variant)
                                                         .setScript(script)
                                                         .build();
-                                list.add(locale);
+                                // Log a WTF here if a repeated locale is found to avoid throwing an
+                                // exception in system server when LocaleList is created below
+                                final int inListIndex = list.indexOf(locale);
+                                if (inListIndex != -1) {
+                                    Slog.wtf(TAG, "Repeated locale (" + list.get(inListIndex) + ")"
+                                            + " found when trying to add: " + locale.toString());
+                                } else {
+                                    list.add(locale);
+                                }
                             } catch (IllformedLocaleException e) {
                                 Slog.e(TAG, "readFromProto error building locale with: "
                                         + "language-" + language + ";country-" + country
@@ -1275,6 +1284,14 @@
                     case (int) WINDOW_CONFIGURATION:
                         windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION);
                         break;
+                    case (int) LOCALE_LIST:
+                        try {
+                            setLocales(LocaleList.forLanguageTags(protoInputStream.readString(
+                                    LOCALE_LIST)));
+                        } catch (Exception e) {
+                            Slog.e(TAG, "error parsing locale list in configuration.", e);
+                        }
+                        break;
                 }
             }
         } finally {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 33e51c9..8e8a81d 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -2169,6 +2169,20 @@
     }
 
     /**
+     * Set camera audio restriction mode.
+     *
+     * @hide
+     */
+    public native final void setAudioRestriction(int mode);
+
+    /**
+     * Get currently applied camera audio restriction mode.
+     *
+     * @hide
+     */
+    public native final int getAudioRestriction();
+
+    /**
      * Image size (width and height dimensions).
      * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
      *             applications.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index bac6522..fb1ece2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -164,6 +164,37 @@
           TEMPLATE_MANUAL})
      public @interface RequestTemplate {};
 
+     /**
+      * No vibration or sound muting for this camera device. This is the default
+      * mode for all camera devices.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_NONE = 0;
+
+     /**
+      * Mute vibration from ringtones, alarms or notifications while this camera device is in use.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_VIBRATION = 1;
+
+     /**
+      * Mute vibration and sound from ringtones, alarms or notifications while this camera device is
+      * in use.
+      *
+      * @see #setCameraAudioRestriction
+      */
+     public static final int AUDIO_RESTRICTION_VIBRATION_SOUND = 3;
+
+     /** @hide */
+     @Retention(RetentionPolicy.SOURCE)
+     @IntDef(prefix = {"AUDIO_RESTRICTION_"}, value =
+         {AUDIO_RESTRICTION_NONE,
+          AUDIO_RESTRICTION_VIBRATION,
+          AUDIO_RESTRICTION_VIBRATION_SOUND})
+     public @interface CAMERA_AUDIO_RESTRICTION {};
+
     /**
      * Get the ID of this camera device.
      *
@@ -536,6 +567,9 @@
      * match one of the supported input sizes for {@link android.graphics.ImageFormat#PRIVATE}
      * format. Otherwise, creating a reprocessable capture session will fail.</p>
      *
+     * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
+     * queued but not yet processed buffers from the input surface.</p>
+     *
      * <p>The guaranteed stream configurations listed in
      * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
      * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
@@ -1208,6 +1242,57 @@
     }
 
     /**
+     * Set audio restriction mode when this CameraDevice is being used.
+     *
+     * <p>Some camera hardware (e.g. devices with optical image stabilization support)
+     * are sensitive to device vibration and video recordings can be ruined by unexpected sounds.
+     * Applications can use this method to suppress vibration or sounds coming from
+     * ringtones, alarms or notifications.
+     * Other vibration or sounds (e.g. media playback or accessibility) will not be muted.</p>
+     *
+     * <p>The mute mode is a system-wide setting. When multiple CameraDevice objects
+     * are setting different modes, the system will pick a the mode that's union of
+     * all modes set by CameraDevice. Applications can also use
+     * {@link #getCameraAudioRestriction} to query current system-wide camera
+     * mute mode in effect.</p>
+     *
+     * <p>The mute settings from this CameraDevice will be automatically removed when the
+     * CameraDevice is closed or the application is disconnected from the camera.</p>
+     *
+     * @param mode An enumeration selecting the audio restriction mode for this camera device.
+     *
+     * @throws IllegalArgumentException if the mode is not supported
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #getCameraAudioRestriction
+     */
+    public void setCameraAudioRestriction(
+            @CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
+     * Get currently applied global camera audio restriction mode.
+     *
+     * <p>Application can use this method to retrieve the system-wide camera audio restriction
+     * settings described in {@link #setCameraAudioRestriction}.</p>
+     *
+     * @return The current system-wide mute mode setting in effect
+     *
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #setCameraAudioRestriction
+     */
+    public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+        throw new UnsupportedOperationException("Subclasses must override this method");
+    }
+
+    /**
      * To be inherited by android.hardware.camera2.* code only.
      * @hide
      */
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6a2fc8a..43c197a 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -880,11 +880,10 @@
          * @throws IllegalArgumentException if the physical camera id is not valid
          *
          * @param key The metadata field to write.
-         * @param value The value to set the field to, which must be of a matching
+         * @param value The value to set the field to, which must be of a matching type to the key.
          * @param physicalCameraId A valid physical camera Id. The valid camera Ids can be obtained
          *                         via calls to {@link CameraCharacteristics#getPhysicalCameraIds}.
          * @return The builder object.
-         * type to the key.
          */
         public <T> Builder setPhysicalCameraKey(@NonNull Key<T> key, T value,
                 @NonNull String physicalCameraId) {
@@ -2790,6 +2789,12 @@
      * output capture result.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
+     * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
+     * sensitivity from last capture result of an auto request for a manual request, in order
+     * to achieve the same brightness in the output image, the application should also
+     * set postRawSensitivityBoost.</p>
      * <p><b>Units</b>: ISO arithmetic units</p>
      * <p><b>Range of valid values:</b><br>
      * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -2800,9 +2805,11 @@
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
      * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ba451e5..1007fd9 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3428,6 +3428,12 @@
      * output capture result.</p>
      * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to
      * OFF; otherwise the auto-exposure algorithm will override this value.</p>
+     * <p>Note that for devices supporting postRawSensitivityBoost, the total sensitivity applied
+     * to the final processed image is the combination of {@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity} and
+     * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost}. In case the application uses the sensor
+     * sensitivity from last capture result of an auto request for a manual request, in order
+     * to achieve the same brightness in the output image, the application should also
+     * set postRawSensitivityBoost.</p>
      * <p><b>Units</b>: ISO arithmetic units</p>
      * <p><b>Range of valid values:</b><br>
      * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}</p>
@@ -3438,9 +3444,11 @@
      *
      * @see CaptureRequest#CONTROL_AE_MODE
      * @see CaptureRequest#CONTROL_MODE
+     * @see CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST
      * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
      * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
      * @see CameraCharacteristics#SENSOR_MAX_ANALOG_SENSITIVITY
+     * @see CaptureRequest#SENSOR_SENSITIVITY
      */
     @PublicKey
     @NonNull
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 68857da9..bcbc337 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1026,34 +1026,35 @@
         // callback is valid
         executor = checkExecutor(executor, callback);
 
-        // Make sure that there all requests have at least 1 surface; all surfaces are non-null;
-        // the surface isn't a physical stream surface for reprocessing request
-        for (CaptureRequest request : requestList) {
-            if (request.getTargets().isEmpty()) {
-                throw new IllegalArgumentException(
-                        "Each request must have at least one Surface target");
-            }
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
 
-            for (Surface surface : request.getTargets()) {
-                if (surface == null) {
-                    throw new IllegalArgumentException("Null Surface targets are not allowed");
+            // Make sure that there all requests have at least 1 surface; all surfaces are non-null;
+            // the surface isn't a physical stream surface for reprocessing request
+            for (CaptureRequest request : requestList) {
+                if (request.getTargets().isEmpty()) {
+                    throw new IllegalArgumentException(
+                            "Each request must have at least one Surface target");
                 }
 
-                for (int i = 0; i < mConfiguredOutputs.size(); i++) {
-                    OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
-                    if (configuration.isForPhysicalCamera()
-                            && configuration.getSurfaces().contains(surface)) {
-                        if (request.isReprocess()) {
-                            throw new IllegalArgumentException(
-                                    "Reprocess request on physical stream is not allowed");
+                for (Surface surface : request.getTargets()) {
+                    if (surface == null) {
+                        throw new IllegalArgumentException("Null Surface targets are not allowed");
+                    }
+
+                    for (int i = 0; i < mConfiguredOutputs.size(); i++) {
+                        OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
+                        if (configuration.isForPhysicalCamera()
+                                && configuration.getSurfaces().contains(surface)) {
+                            if (request.isReprocess()) {
+                                throw new IllegalArgumentException(
+                                        "Reprocess request on physical stream is not allowed");
+                            }
                         }
                     }
                 }
             }
-        }
 
-        synchronized(mInterfaceLock) {
-            checkIfCameraClosedOrInError();
             if (repeating) {
                 stopRepeating();
             }
@@ -2349,14 +2350,21 @@
             if (errorCode == ERROR_CAMERA_BUFFER) {
                 // Because 1 stream id could map to multiple surfaces, we need to specify both
                 // streamId and surfaceId.
-                List<Surface> surfaces =
-                        mConfiguredOutputs.get(resultExtras.getErrorStreamId()).getSurfaces();
-                for (Surface surface : surfaces) {
+                OutputConfiguration config = mConfiguredOutputs.get(
+                        resultExtras.getErrorStreamId());
+                if (config == null) {
+                    Log.v(TAG, String.format(
+                            "Stream %d has been removed. Skipping buffer lost callback",
+                            resultExtras.getErrorStreamId()));
+                    return;
+                }
+                for (Surface surface : config.getSurfaces()) {
                     if (!request.containsTarget(surface)) {
                         continue;
                     }
                     if (DEBUG) {
-                        Log.v(TAG, String.format("Lost output buffer reported for frame %d, target %s",
+                        Log.v(TAG, String.format(
+                                "Lost output buffer reported for frame %d, target %s",
                                 frameNumber, surface));
                     }
                     failureDispatch = new Runnable() {
@@ -2568,4 +2576,21 @@
             Binder.restoreCallingIdentity(ident);
         }
     }
+
+    @Override
+    public void setCameraAudioRestriction(
+            @CAMERA_AUDIO_RESTRICTION int mode) throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
+            mRemoteDevice.setCameraAudioRestriction(mode);
+        }
+    }
+
+    @Override
+    public @CAMERA_AUDIO_RESTRICTION int getCameraAudioRestriction() throws CameraAccessException {
+        synchronized(mInterfaceLock) {
+            checkIfCameraClosedOrInError();
+            return mRemoteDevice.getGlobalAudioRestriction();
+        }
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
index c8ded8d..3660f29 100644
--- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -258,4 +258,22 @@
         }
     }
 
+    public void setCameraAudioRestriction(int mode) throws CameraAccessException {
+        try {
+            mRemoteDevice.setCameraAudioRestriction(mode);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public int getGlobalAudioRestriction() throws CameraAccessException {
+        try {
+            return mRemoteDevice.getGlobalAudioRestriction();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
 }
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index c28cf8f..5d1435a 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -767,6 +767,28 @@
     }
 
     @Override
+    public void setCameraAudioRestriction(int mode) {
+        if (mLegacyDevice.isClosed()) {
+            String err = "Cannot set camera audio restriction, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
+        }
+
+        mLegacyDevice.setAudioRestriction(mode);
+    }
+
+    @Override
+    public int getGlobalAudioRestriction() {
+        if (mLegacyDevice.isClosed()) {
+            String err = "Cannot set camera audio restriction, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
+        }
+
+        return mLegacyDevice.getAudioRestriction();
+    }
+
+    @Override
     public IBinder asBinder() {
         // This is solely intended to be used for in-process binding.
         return null;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 908ed09..fbc9ac32 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -550,6 +550,14 @@
         return lastFrame;
     }
 
+    public void setAudioRestriction(int mode) {
+        mRequestThreadManager.setAudioRestriction(mode);
+    }
+
+    public int getAudioRestriction() {
+        return mRequestThreadManager.getAudioRestriction();
+    }
+
     /**
      * Return {@code true} if the device has been closed.
      */
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 690df1a..32411fb 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -1104,4 +1104,18 @@
         handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
         condition.block();
     }
+
+    public void setAudioRestriction(int mode) {
+        if (mCamera != null) {
+            mCamera.setAudioRestriction(mode);
+        }
+        throw new IllegalStateException("Camera has been released!");
+    }
+
+    public int getAudioRestriction() {
+        if (mCamera != null) {
+            return mCamera.getAudioRestriction();
+        }
+        throw new IllegalStateException("Camera has been released!");
+    }
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 21c49ad..0b25dbd 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -829,23 +829,36 @@
     public interface DeviceConfig {
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for refresh rate in the zone defined by thresholds.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.integer#config_defaultZoneBehavior
+         */
+        String KEY_REFRESH_RATE_IN_ZONE = "refresh_rate_in_zone";
+
+        /**
+         * Key for accessing the display brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
          * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS =
+        String KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS =
                 "peak_refresh_rate_brightness_thresholds";
 
         /**
-         * Key for accessing the 60 hz only regions.
+         * Key for accessing the ambient brightness thresholds for the configured refresh rate zone.
+         * The value will be a pair of comma separated integers representing the minimum and maximum
+         * thresholds of the zone, respectively, in lux.
          *
          * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
-         * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+         * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
          * @hide
          */
-        String KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS = "peak_refresh_rate_ambient_thresholds";
+        String KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS =
+                "peak_refresh_rate_ambient_thresholds";
 
         /**
          * Key for default peak refresh rate
@@ -855,5 +868,16 @@
          * @hide
          */
         String KEY_PEAK_REFRESH_RATE_DEFAULT = "peak_refresh_rate_default";
+
+        /**
+         * Key for controlling which packages are explicitly blocked from running at refresh rates
+         * higher than 60hz. An app may be added to this list if they exhibit performance issues at
+         * higher refresh rates.
+         *
+         * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+         * @see android.R.array#config_highRefreshRateBlacklist
+         * @hide
+         */
+        String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
     }
 }
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 972ae2a..2295d2b 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -734,6 +734,28 @@
             mHotplugEventListeners = new ArrayMap<>();
 
     /**
+     * Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display
+     * status.
+     * @hide
+     */
+    public interface HdmiControlStatusChangeListener {
+        /**
+         * Called when HDMI Control (CEC) is enabled/disabled.
+         *
+         * @param isCecEnabled status of HDMI Control
+         * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
+         * @param isCecAvailable status of CEC support of the connected display (the TV).
+         * {@code true} if supported.
+         *
+         * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
+         **/
+        void onStatusChange(boolean isCecEnabled, boolean isCecAvailable);
+    }
+
+    private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
+            mHdmiControlStatusChangeListeners = new ArrayMap<>();
+
+    /**
      * Listener used to get vendor-specific commands.
      */
     public interface VendorCommandListener {
@@ -826,4 +848,73 @@
             }
         };
     }
+
+    /**
+     * Adds a listener to get informed of {@link HdmiControlStatusChange}.
+     *
+     * <p>To stop getting the notification,
+     * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
+     *
+     * @param listener {@link HdmiControlStatusChangeListener} instance
+     * @see HdmiControlManager#removeHdmiControlStatusChangeListener(
+     * HdmiControlStatusChangeListener)
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
+            Log.e(TAG, "listener is already registered");
+            return;
+        }
+        IHdmiControlStatusChangeListener wrappedListener =
+                getHdmiControlStatusChangeListenerWrapper(listener);
+        mHdmiControlStatusChangeListeners.put(listener, wrappedListener);
+        try {
+            mService.addHdmiControlStatusChangeListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes a listener to stop getting informed of {@link HdmiControlStatusChange}.
+     *
+     * @param listener {@link HdmiControlStatusChangeListener} instance to be removed
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            return;
+        }
+        IHdmiControlStatusChangeListener wrappedListener =
+                mHdmiControlStatusChangeListeners.remove(listener);
+        if (wrappedListener == null) {
+            Log.e(TAG, "tried to remove not-registered listener");
+            return;
+        }
+        try {
+            mService.removeHdmiControlStatusChangeListener(wrappedListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper(
+            final HdmiControlStatusChangeListener listener) {
+        return new IHdmiControlStatusChangeListener.Stub() {
+            @Override
+            public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) {
+                listener.onStatusChange(isCecEnabled, isCecAvailable);
+            }
+        };
+    }
+
 }
diff --git a/core/java/android/hardware/hdmi/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/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index f96f47d..b1134e1 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -71,28 +71,27 @@
      * ID used to target any API call to this paricular module. Module
      * properties are returned by listModules() method.
      *
-     * @hide
      ****************************************************************************/
-    public static class ModuleProperties implements Parcelable {
+    public static final class ModuleProperties implements Parcelable {
         /** Unique module ID provided by the native service */
-        @UnsupportedAppUsage
         public final int id;
 
         /** human readable voice detection engine implementor */
+        @NonNull
         public final String implementor;
 
         /** human readable voice detection engine description */
+        @NonNull
         public final String description;
 
         /** Unique voice engine Id (changes with each version) */
-        @UnsupportedAppUsage
+        @NonNull
         public final UUID uuid;
 
         /** Voice detection engine version */
         public final int version;
 
         /** Maximum number of active sound models */
-        @UnsupportedAppUsage
         public final int maxSoundModels;
 
         /** Maximum number of key phrases */
@@ -120,7 +119,6 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
-        @UnsupportedAppUsage
         ModuleProperties(int id, String implementor, String description,
                 String uuid, int version, int maxSoundModels, int maxKeyphrases,
                 int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
diff --git a/core/java/android/hardware/usb/AccessoryFilter.java b/core/java/android/hardware/usb/AccessoryFilter.java
index 00070fe..f22dad4 100644
--- a/core/java/android/hardware/usb/AccessoryFilter.java
+++ b/core/java/android/hardware/usb/AccessoryFilter.java
@@ -56,6 +56,12 @@
         mVersion = accessory.getVersion();
     }
 
+    public AccessoryFilter(@NonNull AccessoryFilter filter) {
+        mManufacturer = filter.mManufacturer;
+        mModel = filter.mModel;
+        mVersion = filter.mVersion;
+    }
+
     public static AccessoryFilter read(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         String manufacturer = null;
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
index 6f1aff7..da979c0c 100644
--- a/core/java/android/hardware/usb/DeviceFilter.java
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -80,6 +80,17 @@
         mSerialNumber = device.getSerialNumber();
     }
 
+    public DeviceFilter(@NonNull DeviceFilter filter) {
+        mVendorId = filter.mVendorId;
+        mProductId = filter.mProductId;
+        mClass = filter.mClass;
+        mSubclass = filter.mSubclass;
+        mProtocol = filter.mProtocol;
+        mManufacturerName = filter.mManufacturerName;
+        mProductName = filter.mProductName;
+        mSerialNumber = filter.mSerialNumber;
+    }
+
     public static DeviceFilter read(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         int vendorId = -1;
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 299a00a..c8dbd16 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -24,6 +24,7 @@
 import android.hardware.usb.UsbPortStatus;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 
 /** @hide */
 interface IUsbManager
@@ -54,6 +55,26 @@
      */
     void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId);
 
+    /* Adds packages to the set of "denied and don't ask again" launch preferences for a device */
+    void addDevicePackagesToPreferenceDenied(in UsbDevice device, in String[] packageNames, in UserHandle user);
+
+    /* Adds packages to the set of "denied and don't ask again" launch preferences for an accessory */
+    void addAccessoryPackagesToPreferenceDenied(in UsbAccessory accessory, in String[] packageNames, in UserHandle user);
+
+    /* Removes packages from the set of "denied and don't ask again" launch preferences for a device */
+    void removeDevicePackagesFromPreferenceDenied(in UsbDevice device, in String[] packageNames, in UserHandle user);
+
+    /* Removes packages from the set of "denied and don't ask again" launch preferences for an accessory */
+    void removeAccessoryPackagesFromPreferenceDenied(in UsbAccessory device, in String[] packageNames, in UserHandle user);
+
+    /* Sets the persistent permission granted state for USB device
+     */
+    void setDevicePersistentPermission(in UsbDevice device, int uid, in UserHandle user, boolean shouldBeGranted);
+
+    /* Sets the persistent permission granted state for USB accessory
+     */
+    void setAccessoryPersistentPermission(in UsbAccessory accessory, int uid, in UserHandle user, boolean shouldBeGranted);
+
     /* Returns true if the caller has permission to access the device. */
     boolean hasDevicePermission(in UsbDevice device, String packageName);
 
diff --git a/core/java/android/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/os/Binder.java b/core/java/android/os/Binder.java
index 5533721..2c9333b 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
 import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -1029,7 +1030,17 @@
                 Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
                         + (transactionName != null ? transactionName : code));
             }
-            res = onTransact(code, data, reply, flags);
+
+            if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+                AppOpsManager.startNotedAppOpsCollection(callingUid);
+                try {
+                    res = onTransact(code, data, reply, flags);
+                } finally {
+                    AppOpsManager.finishNotedAppOpsCollection();
+                }
+            } else {
+                res = onTransact(code, data, reply, flags);
+            }
         } catch (RemoteException|RuntimeException e) {
             if (observer != null) {
                 observer.callThrewException(callSession, e);
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index c74cef8..b3b4f78 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.util.Log;
 import android.util.SparseIntArray;
 
@@ -506,9 +507,18 @@
             }
         }
 
+        final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
+                AppOpsManager.pauseNotedAppOpsCollection();
+
+        if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isCollectingNotedAppOps()) {
+            flags |= FLAG_COLLECT_NOTED_APP_OPS;
+        }
+
         try {
             return transactNative(code, data, reply, flags);
         } finally {
+            AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
+
             if (transactListener != null) {
                 transactListener.onTransactEnded(session);
             }
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index 83f88ad..12bce8a 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -170,6 +170,11 @@
     int FLAG_ONEWAY             = 0x00000001;
 
     /**
+     * @hide
+     */
+    int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002;
+
+    /**
      * Limit that should be placed on IPC sizes to keep them safely under the
      * transaction buffer limit.
      * @hide
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 7782753..0de09ef 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -21,9 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.Size;
 import android.annotation.UnsupportedAppUsage;
-import android.content.LocaleProto;
 import android.icu.util.ULocale;
-import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -143,26 +141,6 @@
     }
 
     /**
-     * Helper to write LocaleList to a protocol buffer output stream.  Assumes the parent
-     * protobuf has declared the locale as repeated.
-     *
-     * @param protoOutputStream Stream to write the locale to.
-     * @param fieldId Field Id of the Locale as defined in the parent message.
-     * @hide
-     */
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        for (int i = 0; i < mList.length; i++) {
-            final Locale locale = mList[i];
-            final long token = protoOutputStream.start(fieldId);
-            protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage());
-            protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry());
-            protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant());
-            protoOutputStream.write(LocaleProto.SCRIPT, locale.getScript());
-            protoOutputStream.end(token);
-        }
-    }
-
-    /**
      * Retrieves a String representation of the language tags in this list.
      */
     @NonNull
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e1b5542..50487e9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.AppOpsManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -267,7 +268,9 @@
     private static final int EX_UNSUPPORTED_OPERATION = -7;
     private static final int EX_SERVICE_SPECIFIC = -8;
     private static final int EX_PARCELABLE = -9;
-    private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
+    /** @hide */
+    public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; // special; see below
+    private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128;  // special; see below
     // EX_TRANSACTION_FAILED is used exclusively in native code.
     // see libbinder's binder/Status.h
     private static final int EX_TRANSACTION_FAILED = -129;
@@ -1866,6 +1869,8 @@
      * @see #readException
      */
     public final void writeException(@NonNull Exception e) {
+        AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
         int code = 0;
         if (e instanceof Parcelable
                 && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1944,6 +1949,8 @@
      * @see #readException
      */
     public final void writeNoException() {
+        AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+
         // Despite the name of this function ("write no exception"),
         // it should instead be thought of as "write the RPC response
         // header", but because this function name is written out by
@@ -1951,14 +1958,14 @@
         //
         // The response header, in the non-exception case (see also
         // writeException above, also called by the AIDL compiler), is
-        // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+        // either a 0 (the default case), or EX_HAS_STRICTMODE_REPLY_HEADER if
         // StrictMode has gathered up violations that have occurred
         // during a Binder call, in which case we write out the number
         // of violations and their details, serialized, before the
         // actual RPC respons data.  The receiving end of this is
         // readException(), below.
         if (StrictMode.hasGatheredViolations()) {
-            writeInt(EX_HAS_REPLY_HEADER);
+            writeInt(EX_HAS_STRICTMODE_REPLY_HEADER);
             final int sizePosition = dataPosition();
             writeInt(0);  // total size of fat header, to be filled in later
             StrictMode.writeGatheredViolationsToParcel(this);
@@ -2005,7 +2012,13 @@
     @TestApi
     public final int readExceptionCode() {
         int code = readInt();
-        if (code == EX_HAS_REPLY_HEADER) {
+        if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+            AppOpsManager.readAndLogNotedAppops(this);
+            // Read next header or real exception if there is no more header
+            code = readInt();
+        }
+
+        if (code == EX_HAS_STRICTMODE_REPLY_HEADER) {
             int headerSize = readInt();
             if (headerSize == 0) {
                 Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 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/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 7991cd4..f641731 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -76,6 +76,16 @@
         return mServiceManager.listServices(dumpPriority);
     }
 
+    public void registerForNotifications(String name, IServiceCallback cb)
+            throws RemoteException {
+        throw new RemoteException();
+    }
+
+    public void unregisterForNotifications(String name, IServiceCallback cb)
+            throws RemoteException {
+        throw new RemoteException();
+    }
+
     /**
      * Same as mServiceManager but used by apps.
      *
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index cdae72e..b6af829 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -189,6 +189,8 @@
      * Set the value for the given {@code key} to {@code val}.
      *
      * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
+     * @throws RuntimeException if the property cannot be set, for example, if it was blocked by
+     * SELinux. libc will log the underlying reason.
      * @hide
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index 29af17a..dd5e20e 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.IUpdateEngine;
 import android.os.IUpdateEngineCallback;
@@ -320,8 +321,8 @@
      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
      * {@code headerKeyValuePairs} parameters.
      */
-    public void applyPayload(FileDescriptor fd, long offset, long size,
-            String[] headerKeyValuePairs) {
+    public void applyPayload(@NonNull FileDescriptor fd, long offset, long size,
+            @NonNull String[] headerKeyValuePairs) {
         try {
             mUpdateEngine.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
         } catch (RemoteException e) {
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 4e17f7e..0754dc7 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -128,8 +128,9 @@
     @UnsupportedAppUsage
     public static final int AID_CACHE_GID_START = android.os.Process.FIRST_APPLICATION_CACHE_GID;
 
+    /** The userId represented by this UserHandle. */
     @UnsupportedAppUsage
-    final int mHandle;
+    final @UserIdInt int mHandle;
 
     /**
      * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
@@ -270,7 +271,7 @@
     }
 
     /** @hide */
-    public static int getSharedAppGid(int userId, int appId) {
+    public static int getSharedAppGid(@UserIdInt int userId, @AppIdInt int appId) {
         if (appId >= AID_APP_START && appId <= AID_APP_END) {
             return (appId - AID_APP_START) + AID_SHARED_GID_START;
         } else if (appId >= AID_ROOT && appId <= AID_APP_START) {
@@ -300,7 +301,7 @@
     }
 
     /** @hide */
-    public static int getCacheAppGid(int userId, int appId) {
+    public static int getCacheAppGid(@UserIdInt int userId, @AppIdInt int appId) {
         if (appId >= AID_APP_START && appId <= AID_APP_END) {
             return getUid(userId, (appId - AID_APP_START) + AID_CACHE_GID_START);
         } else {
@@ -432,8 +433,8 @@
 
     /** @hide */
     @UnsupportedAppUsage
-    public UserHandle(int h) {
-        mHandle = h;
+    public UserHandle(@UserIdInt int userId) {
+        mHandle = userId;
     }
 
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a7fa96b..af574da 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1404,7 +1404,7 @@
 
     /**
      * Used to check if this process is running under the primary user. The primary user
-     * is the first human user on a device.
+     * is the first human user on a device. This is not supported in headless system user mode.
      *
      * @return whether this process is running under the primary user.
      * @hide
@@ -1794,14 +1794,14 @@
     /**
      * Returns the UserInfo object describing a specific user.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle the user handle of the user whose information is being requested.
+     * @param userId the user handle of the user whose information is being requested.
      * @return the UserInfo object for a specific user.
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo getUserInfo(@UserIdInt int userHandle) {
+    public UserInfo getUserInfo(@UserIdInt int userId) {
         try {
-            return mService.getUserInfo(userHandle);
+            return mService.getUserInfo(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2066,15 +2066,15 @@
      *
      * @param name the user's name
      * @param flags flags that identify the type of user and other properties.
-     * @param userHandle new user will be a profile of this user.
+     * @param userId new user will be a profile of this user.
      *
      * @return the {@link UserInfo} object for the created user, or null if the user
      *         could not be created.
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle) {
-        return createProfileForUser(name, flags, userHandle, null);
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId) {
+        return createProfileForUser(name, flags, userId, null);
     }
 
     /**
@@ -2084,17 +2084,17 @@
      *
      * @param name the user's name
      * @param flags flags that identify the type of user and other properties.
-     * @param userHandle new user will be a profile of this user.
+     * @param userId new user will be a profile of this user.
      * @param disallowedPackages packages that will not be installed in the profile being created.
      *
      * @return the {@link UserInfo} object for the created user, or null if the user
      *         could not be created.
      * @hide
      */
-    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userHandle,
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId,
             String[] disallowedPackages) {
         try {
-            return mService.createProfileForUser(name, flags, userHandle, disallowedPackages);
+            return mService.createProfileForUser(name, flags, userId, disallowedPackages);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2109,9 +2109,9 @@
      * @hide
      */
     public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
-            @UserIdInt int userHandle, String[] disallowedPackages) {
+            @UserIdInt int userId, String[] disallowedPackages) {
         try {
-            return mService.createProfileForUserEvenWhenDisallowed(name, flags, userHandle,
+            return mService.createProfileForUserEvenWhenDisallowed(name, flags, userId,
                     disallowedPackages);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
@@ -2280,12 +2280,12 @@
      * @hide
      * Marks the guest user for deletion to allow a new guest to be created before deleting
      * the current user who is a guest.
-     * @param userHandle
+     * @param userId
      * @return
      */
-    public boolean markGuestForDeletion(@UserIdInt int userHandle) {
+    public boolean markGuestForDeletion(@UserIdInt int userId) {
         try {
-            return mService.markGuestForDeletion(userHandle);
+            return mService.markGuestForDeletion(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2317,16 +2317,16 @@
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} and
      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
      *
-     * @param userHandle the id of the user to become admin
+     * @param userId the id of the user to become admin
      * @hide
      */
     @RequiresPermission(allOf = {
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public void setUserAdmin(@UserIdInt int userHandle) {
+    public void setUserAdmin(@UserIdInt int userId) {
         try {
-            mService.setUserAdmin(userHandle);
+            mService.setUserAdmin(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2337,9 +2337,9 @@
      *
      * @hide
      */
-    public void evictCredentialEncryptionKey(@UserIdInt int userHandle) {
+    public void evictCredentialEncryptionKey(@UserIdInt int userId) {
         try {
-            mService.evictCredentialEncryptionKey(userHandle);
+            mService.evictCredentialEncryptionKey(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2400,9 +2400,9 @@
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public @Nullable String getUserAccount(@UserIdInt int userHandle) {
+    public @Nullable String getUserAccount(@UserIdInt int userId) {
         try {
-            return mService.getUserAccount(userHandle);
+            return mService.getUserAccount(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2416,9 +2416,9 @@
             Manifest.permission.INTERACT_ACROSS_USERS_FULL,
             Manifest.permission.MANAGE_USERS
     })
-    public void setUserAccount(@UserIdInt int userHandle, @Nullable String accountName) {
+    public void setUserAccount(@UserIdInt int userId, @Nullable String accountName) {
         try {
-            mService.setUserAccount(userHandle, accountName);
+            mService.setUserAccount(userId, accountName);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2477,20 +2477,19 @@
     }
 
     /**
-     * Returns list of the profiles of userHandle including
-     * userHandle itself.
+     * Returns list of the profiles of userId including userId itself.
      * Note that this returns both enabled and not enabled profiles. See
      * {@link #getEnabledProfiles(int)} if you need only the enabled ones.
      *
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle profiles of this user will be returned.
+     * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
-    public List<UserInfo> getProfiles(@UserIdInt int userHandle) {
+    public List<UserInfo> getProfiles(@UserIdInt int userId) {
         try {
-            return mService.getProfiles(userHandle, false /* enabledOnly */);
+            return mService.getProfiles(userId, false /* enabledOnly */);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2512,19 +2511,18 @@
     }
 
     /**
-     * Returns list of the profiles of userHandle including
-     * userHandle itself.
+     * Returns list of the profiles of userId including userId itself.
      * Note that this returns only enabled.
      *
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle profiles of this user will be returned.
+     * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
-    public List<UserInfo> getEnabledProfiles(@UserIdInt int userHandle) {
+    public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) {
         try {
-            return mService.getProfiles(userHandle, true /* enabledOnly */);
+            return mService.getProfiles(userId, true /* enabledOnly */);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2584,14 +2582,14 @@
 
     /**
      * Returns the device credential owner id of the profile from
-     * which this method is called, or userHandle if called from a user that
+     * which this method is called, or userId if called from a user that
      * is not a profile.
      *
      * @hide
      */
-    public int getCredentialOwnerProfile(@UserIdInt int userHandle) {
+    public int getCredentialOwnerProfile(@UserIdInt int userId) {
         try {
-            return mService.getCredentialOwnerProfile(userHandle);
+            return mService.getCredentialOwnerProfile(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2604,9 +2602,9 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public UserInfo getProfileParent(@UserIdInt int userHandle) {
+    public UserInfo getProfileParent(@UserIdInt int userId) {
         try {
-            return mService.getProfileParent(userHandle);
+            return mService.getProfileParent(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2785,13 +2783,13 @@
     /**
      * Removes a user and all associated data.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
-     * @param userHandle the integer handle of the user, where 0 is the primary user.
+     * @param userId the integer handle of the user.
      * @hide
      */
     @UnsupportedAppUsage
-    public boolean removeUser(@UserIdInt int userHandle) {
+    public boolean removeUser(@UserIdInt int userId) {
         try {
-            return mService.removeUser(userHandle);
+            return mService.removeUser(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2823,9 +2821,9 @@
      * @see {@link #removeUser(int)}
      * @hide
      */
-    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         try {
-            return mService.removeUserEvenWhenDisallowed(userHandle);
+            return mService.removeUserEvenWhenDisallowed(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2835,13 +2833,13 @@
      * Updates the user's name.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
      *
-     * @param userHandle the user's integer handle
+     * @param userId the user's integer id
      * @param name the new name for the user
      * @hide
      */
-    public void setUserName(@UserIdInt int userHandle, String name) {
+    public void setUserName(@UserIdInt int userId, String name) {
         try {
-            mService.setUserName(userHandle, name);
+            mService.setUserName(userId, name);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2862,13 +2860,13 @@
 
     /**
      * Sets the user's photo.
-     * @param userHandle the user for whom to change the photo.
+     * @param userId the user for whom to change the photo.
      * @param icon the bitmap to set as the photo.
      * @hide
      */
-    public void setUserIcon(@UserIdInt int userHandle, Bitmap icon) {
+    public void setUserIcon(@UserIdInt int userId, Bitmap icon) {
         try {
-            mService.setUserIcon(userHandle, icon);
+            mService.setUserIcon(userId, icon);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2889,15 +2887,15 @@
 
     /**
      * Returns a file descriptor for the user's photo. PNG data can be read from this file.
-     * @param userHandle the user whose photo we want to read.
+     * @param userId the user whose photo we want to read.
      * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
      * @see com.android.internal.util.UserIcons#getDefaultUserIcon for a default.
      * @hide
      */
     @UnsupportedAppUsage
-    public Bitmap getUserIcon(@UserIdInt int userHandle) {
+    public Bitmap getUserIcon(@UserIdInt int userId) {
         try {
-            ParcelFileDescriptor fd = mService.getUserIcon(userHandle);
+            ParcelFileDescriptor fd = mService.getUserIcon(userId);
             if (fd != null) {
                 try {
                     return BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor());
@@ -2999,27 +2997,27 @@
     }
 
     /**
-     * Returns a serial number on this device for a given userHandle. User handles can be recycled
+     * Returns a serial number on this device for a given userId. User handles can be recycled
      * when deleting and creating users, but serial numbers are not reused until the device is wiped.
-     * @param userHandle
-     * @return a serial number associated with that user, or -1 if the userHandle is not valid.
+     * @param userId
+     * @return a serial number associated with that user, or -1 if the userId is not valid.
      * @hide
      */
     @UnsupportedAppUsage
-    public int getUserSerialNumber(@UserIdInt int userHandle) {
+    public int getUserSerialNumber(@UserIdInt int userId) {
         try {
-            return mService.getUserSerialNumber(userHandle);
+            return mService.getUserSerialNumber(userId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Returns a userHandle on this device for a given user serial number. User handles can be
+     * Returns a userId on this device for a given user serial number. User handles can be
      * recycled when deleting and creating users, but serial numbers are not reused until the device
      * is wiped.
      * @param userSerialNumber
-     * @return the userHandle associated with that user serial number, or -1 if the serial number
+     * @return the userId associated with that user serial number, or -1 if the serial number
      * is not valid.
      * @hide
      */
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index ddd949d..a5b71f6 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -137,7 +137,7 @@
             String[] disallowedPackages);
 
     /**
-     * Same as {@link UserManager#removeUser(int userHandle)}, but bypasses the check for
+     * Same as {@link UserManager#removeUser(int userId)}, but bypasses the check for
      * {@link UserManager#DISALLOW_REMOVE_USER} and
      * {@link UserManager#DISALLOW_REMOVE_MANAGED_PROFILE} and does not require the
      * {@link android.Manifest.permission#MANAGE_USERS} permission.
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/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index ec0fe92..654b0f7 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -42,4 +42,5 @@
     void setRuntimePermissionGrantStateByDeviceAdmin(String callerPackageName, String packageName,
                 String permission, int grantState, in AndroidFuture callback);
     void grantOrUpgradeDefaultRuntimePermissions(in AndroidFuture callback);
+    void updateUserSensitive(in AndroidFuture callback);
 }
diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl
index 9fa5f16..60c8811 100644
--- a/core/java/android/permission/IPermissionManager.aidl
+++ b/core/java/android/permission/IPermissionManager.aidl
@@ -19,6 +19,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
 
 /**
@@ -97,4 +98,6 @@
             String packageName, int userId);
 
     boolean isPermissionRevokedByPolicy(String permName, String packageName, int userId);
+
+    List<SplitPermissionInfoParcelable> getSplitPermissions();
 }
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index d012ac7..923d9f8 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -599,4 +599,16 @@
             }
         }, executor);
     }
+
+    /**
+     * @see PermissionControllerService#onUpdateUserSensitive()
+     * @hide
+     */
+    public void updateUserSensitive() {
+        mRemoteService.postAsync(service -> {
+            AndroidFuture<Void> future = new AndroidFuture<>();
+            service.updateUserSensitive(future);
+            return future;
+        });
+    }
 }
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 6dd9b97..7363d77 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -190,6 +190,18 @@
     public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback);
 
     /**
+     * Called by system to update the
+     * {@link PackageManager}{@code .FLAG_PERMISSION_USER_SENSITIVE_WHEN_*} flags for permissions.
+     * <p>
+     * This is typically when creating a new user or upgrading either system or
+     * permission controller package.
+     */
+    @BinderThread
+    public void onUpdateUserSensitive() {
+        throw new AbstractMethodError("Must be overridden in implementing class");
+    }
+
+    /**
      * Set the runtime permission state from a device admin.
      *
      * @param callerPackageName The package name of the admin requesting the change
@@ -380,6 +392,14 @@
 
                 onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(true));
             }
+
+            @Override
+            public void updateUserSensitive(AndroidFuture callback) {
+                Preconditions.checkNotNull(callback, "callback cannot be null");
+
+                onUpdateUserSensitive();
+                callback.complete(null);
+            }
         };
     }
 }
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 5e35958..6c4ee01 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -24,16 +24,18 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.os.RemoteException;
+import android.util.Slog;
 
 import com.android.internal.annotations.Immutable;
-import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * System level service for accessing the permission capabilities of the platform.
@@ -44,6 +46,8 @@
 @SystemApi
 @SystemService(Context.PERMISSION_SERVICE)
 public final class PermissionManager {
+    private static final String TAG = PermissionManager.class.getName();
+
     /** @hide */
     public static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
             "permissions revoked";
@@ -51,19 +55,12 @@
     public static final String KILL_APP_REASON_GIDS_CHANGED =
             "permission grant or revoke changed gids";
 
-
-    /**
-     * {@link android.content.pm.PackageParser} needs access without having a {@link Context}.
-     *
-     * @hide
-     */
-    public static final ArrayList<SplitPermissionInfo> SPLIT_PERMISSIONS =
-            SystemConfig.getInstance().getSplitPermissions();
-
     private final @NonNull Context mContext;
 
     private final IPackageManager mPackageManager;
 
+    private List<SplitPermissionInfo> mSplitPermissionInfos;
+
     /**
      * Creates a new instance.
      *
@@ -131,7 +128,48 @@
      * @return All permissions that are split.
      */
     public @NonNull List<SplitPermissionInfo> getSplitPermissions() {
-        return SPLIT_PERMISSIONS;
+        if (mSplitPermissionInfos != null) {
+            return mSplitPermissionInfos;
+        }
+
+        List<SplitPermissionInfoParcelable> parcelableList;
+        try {
+            parcelableList = ActivityThread.getPermissionManager().getSplitPermissions();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error getting split permissions", e);
+            return Collections.emptyList();
+        }
+
+        mSplitPermissionInfos = splitPermissionInfoListToNonParcelableList(parcelableList);
+
+        return mSplitPermissionInfos;
+    }
+
+    private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
+            List<SplitPermissionInfoParcelable> parcelableList) {
+        final int size = parcelableList.size();
+        List<SplitPermissionInfo> list = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            list.add(new SplitPermissionInfo(parcelableList.get(i)));
+        }
+        return list;
+    }
+
+    /**
+     * Converts a {@link List} of {@link SplitPermissionInfo} into a List of
+     * {@link SplitPermissionInfoParcelable} and returns it.
+     * @hide
+     */
+    public static List<SplitPermissionInfoParcelable> splitPermissionInfoListToParcelableList(
+            List<SplitPermissionInfo> splitPermissionsList) {
+        final int size = splitPermissionsList.size();
+        List<SplitPermissionInfoParcelable> outList = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            SplitPermissionInfo info = splitPermissionsList.get(i);
+            outList.add(new SplitPermissionInfoParcelable(
+                    info.getSplitPermission(), info.getNewPermissions(), info.getTargetSdk()));
+        }
+        return outList;
     }
 
     /**
@@ -140,44 +178,40 @@
      */
     @Immutable
     public static final class SplitPermissionInfo {
-        private final @NonNull String mSplitPerm;
-        private final @NonNull List<String> mNewPerms;
-        private final int mTargetSdk;
+        private @NonNull final SplitPermissionInfoParcelable mSplitPermissionInfoParcelable;
 
         @Override
         public boolean equals(@Nullable Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             SplitPermissionInfo that = (SplitPermissionInfo) o;
-            return mTargetSdk == that.mTargetSdk
-                    && mSplitPerm.equals(that.mSplitPerm)
-                    && mNewPerms.equals(that.mNewPerms);
+            return mSplitPermissionInfoParcelable.equals(that.mSplitPermissionInfoParcelable);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mSplitPerm, mNewPerms, mTargetSdk);
+            return mSplitPermissionInfoParcelable.hashCode();
         }
 
         /**
          * Get the permission that is split.
          */
         public @NonNull String getSplitPermission() {
-            return mSplitPerm;
+            return mSplitPermissionInfoParcelable.getSplitPermission();
         }
 
         /**
          * Get the permissions that are added.
          */
         public @NonNull List<String> getNewPermissions() {
-            return mNewPerms;
+            return mSplitPermissionInfoParcelable.getNewPermissions();
         }
 
         /**
          * Get the target API level when the permission was split.
          */
         public int getTargetSdk() {
-            return mTargetSdk;
+            return mSplitPermissionInfoParcelable.getTargetSdk();
         }
 
         /**
@@ -191,9 +225,11 @@
          */
         public SplitPermissionInfo(@NonNull String splitPerm, @NonNull List<String> newPerms,
                 int targetSdk) {
-            mSplitPerm = splitPerm;
-            mNewPerms = newPerms;
-            mTargetSdk = targetSdk;
+            this(new SplitPermissionInfoParcelable(splitPerm, newPerms, targetSdk));
+        }
+
+        private SplitPermissionInfo(@NonNull SplitPermissionInfoParcelable parcelable) {
+            mSplitPermissionInfoParcelable = parcelable;
         }
     }
 }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0827fd6..0973a64 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -42,7 +42,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.telephony.CallerInfo;
+import android.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
 
 import java.util.List;
@@ -728,10 +728,11 @@
             String accountAddress = getLogAccountAddress(context, accountHandle);
 
             int numberPresentation = getLogNumberPresentation(number, presentation);
+            String name = (ci != null) ? ci.getName() : "";
             if (numberPresentation != PRESENTATION_ALLOWED) {
                 number = "";
                 if (ci != null) {
-                    ci.name = "";
+                    name = "";
                 }
             }
 
@@ -760,9 +761,7 @@
             values.put(PHONE_ACCOUNT_ID, accountId);
             values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
             values.put(NEW, Integer.valueOf(1));
-            if ((ci != null) && (ci.name != null)) {
-                values.put(CACHED_NAME, ci.name);
-            }
+            values.put(CACHED_NAME, name);
             values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0);
 
             if (callType == MISSED_TYPE) {
@@ -773,7 +772,7 @@
             values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
             values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
 
-            if ((ci != null) && (ci.contactIdOrZero > 0)) {
+            if ((ci != null) && (ci.getContactId() > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
                 // contacts may have the same number.
@@ -787,17 +786,18 @@
                     cursor = resolver.query(Phone.CONTENT_URI,
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
-                            new String[] { String.valueOf(ci.contactIdOrZero),
+                            new String[] { String.valueOf(ci.getContactId()),
                                     normalizedPhoneNumber},
                             null);
                 } else {
-                    final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
+                    final String phoneNumber = ci.getPhoneNumber() != null
+                        ? ci.getPhoneNumber() : number;
                     cursor = resolver.query(
                             Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
                                     Uri.encode(phoneNumber)),
                             new String[] { Phone._ID },
                             Phone.CONTACT_ID + " =?",
-                            new String[] { String.valueOf(ci.contactIdOrZero) },
+                            new String[] { String.valueOf(ci.getContactId()) },
                             null);
                 }
 
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4b9daf1..5e201e4 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -367,15 +367,6 @@
          */
         String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
                 "system_gesture_exclusion_log_debounce_millis";
-
-        /**
-         * Key for controlling which packages are explicitly blocked from running at refresh rates
-         * higher than 90hz.
-         *
-         * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
-         * @hide
-         */
-        String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
     }
 
     private static final Object sLock = new Object();
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 dd3942e..8b20f0b 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();
@@ -7910,8 +7906,10 @@
          * Whether the notification bubbles are globally enabled
          * The value is boolean (1 or 0).
          * @hide
+         * @deprecated use {@link Global#NOTIFICATION_BUBBLES} instead.
          */
         @TestApi
+        @Deprecated
         public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
 
         /**
@@ -8235,6 +8233,14 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
 
         /**
+         * Whether the notification bubbles are globally enabled
+         * The value is boolean (1 or 0).
+         * @hide
+         */
+        @TestApi
+        public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
+
+        /**
          * Whether users are allowed to add more users or guest from lockscreen.
          * <p>
          * Type: int
@@ -8801,13 +8807,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
@@ -12917,8 +12916,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";
@@ -12932,15 +12930,13 @@
         public static final String MULTI_SIM_VOICE_PROMPT = "multi_sim_voice_prompt";
 
         /**
-          * Subscription to be used for data call on a multi sim device. The supported values
-          * are 0 = SUB1, 1 = SUB2 and etc.
+          * Subscription Id to be used for data call on a multi sim device.
           * @hide
           */
         public static final String MULTI_SIM_DATA_CALL_SUBSCRIPTION = "multi_sim_data_call";
 
         /**
-          * Subscription to be used for SMS on a multi sim device. The supported values
-          * are 0 = SUB1, 1 = SUB2 and etc.
+          * Subscription Id to be used for SMS on a multi sim device.
           * @hide
           */
         public static final String MULTI_SIM_SMS_SUBSCRIPTION = "multi_sim_sms";
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 773be80..ae2836f 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -4,17 +4,21 @@
             "name": "CtsProviderTestCases",
             "options": [
                 {
-                    "include-annotation": "android.platform.test.annotations.Presubmit"
+                    "exclude-filter": "android.provider.cts.SettingsPanelTest"
                 }
             ]
         },
         {
-            "name": "SettingsProviderTest",
-            "options": [
-                {
-                    "include-annotation": "android.platform.test.annotations.Presubmit"
-                }
-            ]
+            "name": "CalendarProviderTests"
+        },
+        {
+            "name": "ContactsProviderTests"
+        },
+        {
+            "name": "MediaProviderTests"
+        },
+        {
+            "name": "SettingsProviderTest"
         }
     ]
 }
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/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/core/java/android/service/carrier/IApnSourceService.aidl
similarity index 93%
rename from telephony/java/com/android/internal/telephony/IApnSourceService.aidl
rename to core/java/android/service/carrier/IApnSourceService.aidl
index 34c9067..fadd2ff 100644
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ b/core/java/android/service/carrier/IApnSourceService.aidl
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package android.service.carrier;
 
 import android.content.ContentValues;
 
+/** @hide */
 interface IApnSourceService {
     /** Retreive APNs. */
     ContentValues[] getApns(int subId);
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index d2f22bf..ff8b135 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -29,7 +29,6 @@
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
 import android.telephony.euicc.EuiccManager.OtaStatus;
-import android.util.ArraySet;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -252,18 +251,6 @@
     public static final int RESULT_FIRST_USER = 1;
 
     /**
-     * List of all valid resolution actions for validation purposes.
-     * @hide
-     */
-    public static final ArraySet<String> RESOLUTION_ACTIONS;
-    static {
-        RESOLUTION_ACTIONS = new ArraySet<>();
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
-        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_RESOLVABLE_ERRORS);
-    }
-
-    /**
      * Boolean extra for resolution actions indicating whether the user granted consent.
      * This is used and set by the implementation and used in {@code EuiccOperation}.
      */
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 78e30ab..85f13d5 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1522,6 +1522,7 @@
         private ArrayList<Notification.Action> mSmartActions;
         private ArrayList<CharSequence> mSmartReplies;
         private boolean mCanBubble;
+        private boolean mVisuallyInterruptive;
 
         private static final int PARCEL_VERSION = 2;
 
@@ -1553,6 +1554,7 @@
             out.writeTypedList(mSmartActions, flags);
             out.writeCharSequenceList(mSmartReplies);
             out.writeBoolean(mCanBubble);
+            out.writeBoolean(mVisuallyInterruptive);
         }
 
         /** @hide */
@@ -1585,6 +1587,7 @@
             mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
             mSmartReplies = in.readCharSequenceList();
             mCanBubble = in.readBoolean();
+            mVisuallyInterruptive = in.readBoolean();
         }
 
 
@@ -1772,6 +1775,11 @@
         }
 
         /** @hide */
+        public boolean visuallyInterruptive() {
+            return mVisuallyInterruptive;
+        }
+
+        /** @hide */
         public boolean isNoisy() {
             return mNoisy;
         }
@@ -1787,7 +1795,8 @@
                 ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
                 int userSentiment, boolean hidden, long lastAudiblyAlertedMs,
                 boolean noisy, ArrayList<Notification.Action> smartActions,
-                ArrayList<CharSequence> smartReplies, boolean canBubble) {
+                ArrayList<CharSequence> smartReplies, boolean canBubble,
+                boolean visuallyInterruptive) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1808,6 +1817,7 @@
             mSmartActions = smartActions;
             mSmartReplies = smartReplies;
             mCanBubble = canBubble;
+            mVisuallyInterruptive = visuallyInterruptive;
         }
 
         /**
@@ -1832,7 +1842,8 @@
                     other.mNoisy,
                     other.mSmartActions,
                     other.mSmartReplies,
-                    other.mCanBubble);
+                    other.mCanBubble,
+                    other.mVisuallyInterruptive);
         }
 
         /**
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 205df7e..b8378a3 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -170,10 +170,7 @@
 
     /**
      * Returns true if application asked that this notification be part of a group.
-     *
-     * @hide
      */
-    @SystemApi
     public boolean isAppGroup() {
         if (getNotification().getGroup() != null || getNotification().getSortKey() != null) {
             return true;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index bfab580..937990f7 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1639,7 +1639,7 @@
         @UnsupportedAppUsage
         public String name;              // required for automatic
         @UnsupportedAppUsage
-        public int zenMode;
+        public int zenMode;             // ie: Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
         @UnsupportedAppUsage
         public Uri conditionId;          // required for automatic
         public Condition condition;      // optional
diff --git a/core/java/android/service/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 8665106..1c50d73 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -276,6 +276,7 @@
         final int runCount = mDirections.getRunCount();
         for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -360,6 +361,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
@@ -417,6 +419,7 @@
         float h = 0;
         for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
+            if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 07cecd3..74cff20 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -67,20 +67,19 @@
      * found.
      */
     private static android.icu.util.TimeZone getIcuTimeZone(
-            int offset, boolean dst, long when, String country) {
-        if (country == null) {
+            int offsetMillis, boolean isDst, long whenMillis, String countryIso) {
+        if (countryIso == null) {
             return null;
         }
 
         android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
         CountryTimeZones countryTimeZones =
-                TimeZoneFinder.getInstance().lookupCountryTimeZones(country);
+                TimeZoneFinder.getInstance().lookupCountryTimeZones(countryIso);
         if (countryTimeZones == null) {
             return null;
         }
-
-        CountryTimeZones.OffsetResult offsetResult =
-                countryTimeZones.lookupByOffsetWithBias(offset, dst, when, bias);
+        CountryTimeZones.OffsetResult offsetResult = countryTimeZones.lookupByOffsetWithBias(
+                offsetMillis, isDst, null /* dstOffsetMillis */, whenMillis, bias);
         return offsetResult != null ? offsetResult.mTimeZone : null;
     }
 
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 2efebf6..7e22dd9 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -18,6 +18,7 @@
 
 import static android.view.DisplayInfoProto.APP_HEIGHT;
 import static android.view.DisplayInfoProto.APP_WIDTH;
+import static android.view.DisplayInfoProto.FLAGS;
 import static android.view.DisplayInfoProto.LOGICAL_HEIGHT;
 import static android.view.DisplayInfoProto.LOGICAL_WIDTH;
 import static android.view.DisplayInfoProto.NAME;
@@ -708,6 +709,7 @@
         protoOutputStream.write(APP_WIDTH, appWidth);
         protoOutputStream.write(APP_HEIGHT, appHeight);
         protoOutputStream.write(NAME, name);
+        protoOutputStream.write(FLAGS, flags);
         protoOutputStream.end(token);
     }
 
diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java
index fcc14c1..fffb323e 100644
--- a/core/java/android/view/GestureExclusionTracker.java
+++ b/core/java/android/view/GestureExclusionTracker.java
@@ -44,7 +44,7 @@
         while (i.hasNext()) {
             final GestureExclusionViewInfo info = i.next();
             final View v = info.getView();
-            if (v == null || !v.isAttachedToWindow() || !v.isShown()) {
+            if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) {
                 mGestureExclusionViewsChanged = true;
                 i.remove();
                 continue;
@@ -123,7 +123,7 @@
         public int update() {
             final View excludedView = getView();
             if (excludedView == null || !excludedView.isAttachedToWindow()
-                    || !excludedView.isShown()) return GONE;
+                    || !excludedView.isAggregatedVisible()) return GONE;
             final List<Rect> localRects = excludedView.getSystemGestureExclusionRects();
             final List<Rect> newRects = new ArrayList<>(localRects.size());
             for (Rect src : localRects) {
diff --git a/core/java/android/view/IPinnedStackController.aidl b/core/java/android/view/IPinnedStackController.aidl
index d2dcb56..f1d152b 100644
--- a/core/java/android/view/IPinnedStackController.aidl
+++ b/core/java/android/view/IPinnedStackController.aidl
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import android.graphics.Rect;
+
 /**
  * An interface to the PinnedStackController to update it of state changes, and to query
  * information based on the current state.
@@ -30,15 +32,17 @@
     oneway void setIsMinimized(boolean isMinimized);
 
     /**
-     * Notifies the controller of the current min edge size, this is needed to allow the system to
-     * properly calculate the aspect ratio of the expanded PIP.  The given {@param minEdgeSize} is
-     * always bounded to be larger than the default minEdgeSize, so the caller can call this method
-     * with 0 to reset to the default size.
-     */
-    oneway void setMinEdgeSize(int minEdgeSize);
-
-    /**
      * @return what WM considers to be the current device rotation.
      */
     int getDisplayRotation();
+
+    /**
+     * Notifies the controller to actually start the PiP animation.
+     * The bounds would be calculated based on the last save reentry fraction internally.
+     * {@param destinationBounds} is the stack bounds of the final PiP window
+     * and {@param sourceRectHint} is the source bounds hint used when entering picture-in-picture,
+     * expect the same bound passed via IPinnedStackListener#onPrepareAnimation.
+     * {@param animationDuration} suggests the animation duration transitioning to PiP window.
+     */
+    void startAnimation(in Rect destinationBounds, in Rect sourceRectHint, int animationDuration);
 }
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 2da353b..806d81e 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 
 /**
@@ -36,18 +38,13 @@
     /**
      * Called when the window manager has detected a change that would cause the movement bounds
      * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides
-     * the components that allow the listener to calculate the movement bounds itself. The
-     * {@param normalBounds} are also the default bounds that the PiP would be entered in its
-     * current state with the aspect ratio applied.  The {@param animatingBounds} are provided
-     * to indicate the current target bounds of the pinned stack (the final bounds if animating,
-     * the current bounds if not), which may be helpful in calculating dependent animation bounds.
-     *
-     * The {@param displayRotation} is provided so that the client can verify when making certain
-     * calls that it will not provide stale information based on an old display rotation (ie. if
-     * the WM has changed in the mean time but the client has not received onMovementBoundsChanged).
+     * the components that allow the listener to calculate the movement bounds itself.
+     * The {@param animatingBounds} are provided to indicate the current target bounds of the
+     * pinned stack (the final bounds if animating, the current bounds if not),
+     * which may be helpful in calculating dependent animation bounds.
      */
-    void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation);
+    void onMovementBoundsChanged(in Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment);
 
     /**
      * Called when window manager decides to adjust the pinned stack bounds because of the IME, or
@@ -76,4 +73,47 @@
      * is first registered to allow the listener to synchronized its state with the controller.
      */
     void onActionsChanged(in ParceledListSlice actions);
+
+    /**
+     * Called by the window manager to notify the listener to save the reentry fraction,
+     * typically when an Activity leaves PiP (picture-in-picture) mode to fullscreen.
+     * {@param componentName} represents the application component of PiP window
+     * while {@param bounds} is the current PiP bounds used to calculate the
+     * reentry snap fraction.
+     */
+    void onSaveReentrySnapFraction(in ComponentName componentName, in Rect bounds);
+
+    /**
+     * Called by the window manager to notify the listener to reset saved reentry fraction,
+     * typically when an Activity enters PiP (picture-in-picture) mode from fullscreen.
+     * {@param componentName} represents the application component of PiP window.
+     */
+    void onResetReentrySnapFraction(in ComponentName componentName);
+
+    /**
+     * Called when the window manager has detected change on DisplayInfo,  or
+     * when the listener is first registered to allow the listener to synchronized its state with
+     * the controller.
+     */
+    void onDisplayInfoChanged(in DisplayInfo displayInfo);
+
+    /**
+     * Called by the window manager at the beginning of a configuration update cascade
+     * since the metrics from these resources are used for bounds calculations.
+     */
+    void onConfigurationChanged();
+
+    /**
+     * Called by the window manager when the aspect ratio is reset.
+     */
+    void onAspectRatioChanged(float aspectRatio);
+
+    /**
+     * Called by the window manager to notify the listener to prepare for PiP animation.
+     * Internally, the target bounds would be calculated from the given {@param aspectRatio}
+     * and {@param bounds}, the saved reentry snap fraction also contributes.
+     * Caller would wait for a IPinnedStackController#startAnimation callback to actually
+     * start the animation, see details in IPinnedStackController.
+     */
+    void onPrepareAnimation(in Rect sourceRectHint, float aspectRatio, in Rect bounds);
 }
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index 724d96e..6eb90fc 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -54,6 +54,6 @@
      */
     @UnsupportedAppUsage
     void onAnimationStart(in IRecentsAnimationController controller,
-            in RemoteAnimationTarget[] apps, in Rect homeContentInsets,
-            in Rect minimizedHomeBounds) = 2;
+            in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
+            in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
 }
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 73b023c..7b35aa2 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -34,7 +34,7 @@
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
     @UnsupportedAppUsage
-    void onAnimationStart(in RemoteAnimationTarget[] apps,
+    void onAnimationStart(in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
             in IRemoteAnimationFinishedCallback finishedCallback);
 
     /**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bb9867a..1c32948 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -640,4 +640,14 @@
      * native InputManager before proceeding with tests.
      */
     void syncInputTransactions();
+
+    /**
+     * Returns whether SurfaceFlinger layer tracing is enabled.
+     */
+    boolean isLayerTracing();
+
+    /**
+     * Enables/disables SurfaceFlinger layer tracing.
+     */
+    void setLayerTracing(boolean enabled);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index f73f28a..6ce5ac4 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -31,7 +31,7 @@
 import android.view.InsetsState;
 import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.Transaction;
+import android.view.SurfaceControl.Transaction;
 
 import java.util.List;
 
@@ -156,7 +156,7 @@
      * is null if there is no sync required.
      */
     @UnsupportedAppUsage
-    void finishDrawing(IWindow window, in Transaction postDrawTransaction);
+    void finishDrawing(IWindow window, in SurfaceControl.Transaction postDrawTransaction);
 
     @UnsupportedAppUsage
     void setInTouchMode(boolean showFocus);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 50ef91f..341c214 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -26,7 +26,6 @@
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.os.UidProto.Sync;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -40,7 +39,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.function.Function;
 import java.util.function.Supplier;
 
 /**
@@ -238,7 +236,12 @@
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
             state.getSource(source.getType()).setFrame(mTmpFrame);
-            surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f, inset != 0));
+
+            // If the system is controlling the insets source, the leash can be null.
+            if (leash != null) {
+                surfaceParams.add(new SurfaceParams(leash, 1f /* alpha */, mTmpMatrix,
+                        null /* windowCrop */, 0 /* layer */, 0f /* cornerRadius*/, inset != 0));
+            }
         }
     }
 
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d0ecae9..c798d85 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -602,4 +602,22 @@
             mAnimCallbackScheduled = true;
         }
     }
+
+    @Override
+    public void setSystemBarsAppearance(@Appearance int appearance) {
+        if (mViewRoot.mWindowAttributes.insetsFlags.appearance != appearance) {
+            mViewRoot.mWindowAttributes.insetsFlags.appearance = appearance;
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
+
+    @Override
+    public void setSystemBarsBehavior(@Behavior int behavior) {
+        if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
+            mViewRoot.mWindowAttributes.insetsFlags.behavior = behavior;
+            mViewRoot.mWindowAttributesChanged = true;
+            mViewRoot.scheduleTraversals();
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java
new file mode 100644
index 0000000..276e80a
--- /dev/null
+++ b/core/java/android/view/InsetsFlags.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_SIDE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_TOP_BAR;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_BARS;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
+import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
+
+import android.view.WindowInsetsController.Appearance;
+import android.view.WindowInsetsController.Behavior;
+
+/**
+ * Contains the information about {@link Appearance} and {@link Behavior} of system windows which
+ * can produce insets. This is for carrying the request from a client to the system server.
+ * @hide
+ */
+public class InsetsFlags {
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_OPAQUE_BARS,
+                    equals = APPEARANCE_OPAQUE_BARS,
+                    name = "OPAQUE_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LOW_PROFILE_BARS,
+                    equals = APPEARANCE_LOW_PROFILE_BARS,
+                    name = "LOW_PROFILE_BARS"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_TOP_BAR,
+                    equals = APPEARANCE_LIGHT_TOP_BAR,
+                    name = "LIGHT_TOP_BAR"),
+            @ViewDebug.FlagToString(
+                    mask = APPEARANCE_LIGHT_SIDE_BARS,
+                    equals = APPEARANCE_LIGHT_SIDE_BARS,
+                    name = "LIGHT_SIDE_BARS")
+    })
+    public @Appearance int appearance;
+
+    @ViewDebug.ExportedProperty(flagMapping = {
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_SHOW_BARS_BY_SWIPE,
+                    equals = BEHAVIOR_SHOW_BARS_BY_SWIPE,
+                    name = "SHOW_BARS_BY_SWIPE"),
+            @ViewDebug.FlagToString(
+                    mask = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    equals = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
+                    name = "SHOW_TRANSIENT_BARS_BY_SWIPE")
+    })
+    public @Behavior int behavior;
+}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 9edccb3..08d45a7 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -167,7 +167,7 @@
     }
 
     private void applyHiddenToControl() {
-        if (mSourceControl == null) {
+        if (mSourceControl == null || mSourceControl.getLeash() == null) {
             return;
         }
 
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 4940981..4919074 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Point;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,10 +29,10 @@
 public class InsetsSourceControl implements Parcelable {
 
     private final @InternalInsetType int mType;
-    private final SurfaceControl mLeash;
+    private final @Nullable SurfaceControl mLeash;
     private final Point mSurfacePosition;
 
-    public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash,
+    public InsetsSourceControl(@InternalInsetType int type, @Nullable SurfaceControl leash,
             Point surfacePosition) {
         mType = type;
         mLeash = leash;
@@ -42,7 +43,13 @@
         return mType;
     }
 
-    public SurfaceControl getLeash() {
+    /**
+     * Gets the leash for controlling insets source. If the system is controlling the insets source,
+     * for example, transient bars, the client will receive fake controls without leash in it.
+     *
+     * @return the leash.
+     */
+    public @Nullable SurfaceControl getLeash() {
         return mLeash;
     }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index b2f3f5e..d54e9d5 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -30,6 +30,7 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.RemoteViews;
 
 import com.android.internal.R;
@@ -54,6 +55,7 @@
     private OnClickListener mExpandClickListener;
     private OnClickListener mAppOpsListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+    private LinearLayout mTransferChip;
     private ImageView mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
@@ -116,6 +118,7 @@
         mAppName = findViewById(com.android.internal.R.id.app_name_text);
         mHeaderText = findViewById(com.android.internal.R.id.header_text);
         mSecondaryHeaderText = findViewById(com.android.internal.R.id.header_text_secondary);
+        mTransferChip = findViewById(com.android.internal.R.id.media_seamless);
         mExpandButton = findViewById(com.android.internal.R.id.expand_button);
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
@@ -148,9 +151,11 @@
             int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
                     lp.topMargin + lp.bottomMargin, lp.height);
             child.measure(childWidthSpec, childHeightSpec);
+            // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps) {
+                    || child == mAppOps
+                    || child == mTransferChip) {
                 iconWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
             } else {
                 totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
@@ -211,9 +216,11 @@
             int layoutRight;
             int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
             int bottom = top + childHeight;
+            // Icons that should go at the end
             if ((child == mExpandButton && mShowExpandButtonAtEnd)
                     || child == mProfileBadge
-                    || child == mAppOps) {
+                    || child == mAppOps
+                    || child == mTransferChip) {
                 if (end == getMeasuredWidth()) {
                     layoutRight = end - mContentEndMargin;
                 } else {
diff --git a/core/java/android/view/SurfaceControl.aidl b/core/java/android/view/SurfaceControl.aidl
index 744ead2..6c51ae9 100644
--- a/core/java/android/view/SurfaceControl.aidl
+++ b/core/java/android/view/SurfaceControl.aidl
@@ -17,3 +17,4 @@
 package android.view;
 
 parcelable SurfaceControl;
+parcelable SurfaceControl.Transaction;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 522ad64..d5559aa 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -105,8 +105,6 @@
             long relativeToObject, int zorder);
     private static native void nativeSetPosition(long transactionObj, long nativeObject,
             float x, float y);
-    private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
-            long nativeObject);
     private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long transactionObj,
             long nativeObject, Region region);
@@ -1104,16 +1102,6 @@
     /**
      * @hide
      */
-    public void setGeometryAppliesWithResize() {
-        checkNotReleased();
-        synchronized(SurfaceControl.class) {
-            sGlobalTransaction.setGeometryAppliesWithResize(this);
-        }
-    }
-
-    /**
-     * @hide
-     */
     public void setBufferSize(int w, int h) {
         checkNotReleased();
         synchronized(SurfaceControl.class) {
@@ -2488,20 +2476,6 @@
         }
 
         /**
-         * If the buffer size changes in this transaction, position and crop updates specified
-         * in this transaction will not complete until a buffer of the new size
-         * arrives. As transform matrix and size are already frozen in this fashion,
-         * this enables totally freezing the surface until the resize has completed
-         * (at which point the geometry influencing aspects of this transaction will then occur)
-         * @hide
-         */
-        public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
-            sc.checkNotReleased();
-            nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
-            return this;
-        }
-
-        /**
          * Sets the security of the surface.  Setting the flag is equivalent to creating the
          * Surface with the {@link #SECURE} flag.
          * @hide
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 90e69f3..262b9e5 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -136,6 +136,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     boolean mIsCreating = false;
     private volatile boolean mRtHandlingPositionUpdates = false;
+    private volatile boolean mRtReleaseSurfaces = false;
 
     private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener =
             this::updateSurface;
@@ -658,7 +659,14 @@
     }
 
     private void releaseSurfaces() {
+        mSurfaceAlpha = 1f;
+
         synchronized (mSurfaceControlLock) {
+            if (mRtHandlingPositionUpdates) {
+                mRtReleaseSurfaces = true;
+                return;
+            }
+
             if (mSurfaceControl != null) {
                 mTmpTransaction.remove(mSurfaceControl);
                 mSurfaceControl = null;
@@ -669,7 +677,6 @@
             }
             mTmpTransaction.apply();
         }
-        mSurfaceAlpha = 1f;
     }
 
     /** @hide */
@@ -1084,7 +1091,9 @@
             // the synchronization would violate the rule that RT must never block
             // on the UI thread which would open up potential deadlocks. The risk of
             // a single-frame desync is therefore preferable for now.
-            mRtHandlingPositionUpdates = true;
+            synchronized(mSurfaceControlLock) {
+                mRtHandlingPositionUpdates = true;
+            }
             if (mRTLastReportedPosition.left == left
                     && mRTLastReportedPosition.top == top
                     && mRTLastReportedPosition.right == right
@@ -1126,6 +1135,18 @@
                         frameNumber);
             }
             mRtTransaction.hide(mSurfaceControl);
+
+            synchronized (mSurfaceControlLock) {
+                if (mRtReleaseSurfaces) {
+                    mRtReleaseSurfaces = false;
+                    mRtTransaction.remove(mSurfaceControl);
+                    mRtTransaction.remove(mBackgroundControl);
+                    mSurfaceControl = null;
+                    mBackgroundControl = null;
+                }
+                mRtHandlingPositionUpdates = false;
+            }
+
             mRtTransaction.apply();
         }
     };
diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
index 85457cb..a6536f5d 100644
--- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java
+++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java
@@ -30,6 +30,14 @@
  */
 public class SyncRtSurfaceTransactionApplier {
 
+    public static final int FLAG_ALL = 0xffffffff;
+    public static final int FLAG_ALPHA = 1;
+    public static final int FLAG_MATRIX = 1 << 1;
+    public static final int FLAG_WINDOW_CROP = 1 << 2;
+    public static final int FLAG_LAYER = 1 << 3;
+    public static final int FLAG_CORNER_RADIUS = 1 << 4;
+    public static final int FLAG_VISIBILITY = 1 << 5;
+
     private final Surface mTargetSurface;
     private final ViewRootImpl mTargetViewRootImpl;
     private final float[] mTmpFloat9 = new float[9];
@@ -72,15 +80,27 @@
     }
 
     public static void applyParams(Transaction t, SurfaceParams params, float[] tmpFloat9) {
-        t.setMatrix(params.surface, params.matrix, tmpFloat9);
-        t.setWindowCrop(params.surface, params.windowCrop);
-        t.setAlpha(params.surface, params.alpha);
-        t.setLayer(params.surface, params.layer);
-        t.setCornerRadius(params.surface, params.cornerRadius);
-        if (params.visible) {
-            t.show(params.surface);
-        } else {
-            t.hide(params.surface);
+        if ((params.flags & FLAG_MATRIX) != 0) {
+            t.setMatrix(params.surface, params.matrix, tmpFloat9);
+        }
+        if ((params.flags & FLAG_WINDOW_CROP) != 0) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
+        if ((params.flags & FLAG_ALPHA) != 0) {
+            t.setAlpha(params.surface, params.alpha);
+        }
+        if ((params.flags & FLAG_LAYER) != 0) {
+            t.setLayer(params.surface, params.layer);
+        }
+        if ((params.flags & FLAG_CORNER_RADIUS) != 0) {
+            t.setCornerRadius(params.surface, params.cornerRadius);
+        }
+        if ((params.flags & FLAG_VISIBILITY) != 0) {
+            if (params.visible) {
+                t.show(params.surface);
+            } else {
+                t.hide(params.surface);
+            }
         }
     }
 
@@ -115,17 +135,95 @@
 
     public static class SurfaceParams {
 
-        /**
-         * Constructs surface parameters to be applied when the current view state gets pushed to
-         * RenderThread.
-         *
-         * @param surface The surface to modify.
-         * @param alpha Alpha to apply.
-         * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
-         */
-        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+        public static class Builder {
+            final SurfaceControl surface;
+            int flags;
+            float alpha;
+            float cornerRadius;
+            Matrix matrix;
+            Rect windowCrop;
+            int layer;
+            boolean visible;
+
+            /**
+             * @param surface The surface to modify.
+             */
+            public Builder(SurfaceControl surface) {
+                this.surface = surface;
+            }
+
+            /**
+             * @param alpha The alpha value to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withAlpha(float alpha) {
+                this.alpha = alpha;
+                flags |= FLAG_ALPHA;
+                return this;
+            }
+
+            /**
+             * @param matrix The matrix to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withMatrix(Matrix matrix) {
+                this.matrix = matrix;
+                flags |= FLAG_MATRIX;
+                return this;
+            }
+
+            /**
+             * @param windowCrop The window crop to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withWindowCrop(Rect windowCrop) {
+                this.windowCrop = windowCrop;
+                flags |= FLAG_WINDOW_CROP;
+                return this;
+            }
+
+            /**
+             * @param layer The layer to assign the surface.
+             * @return this Builder
+             */
+            public Builder withLayer(int layer) {
+                this.layer = layer;
+                flags |= FLAG_LAYER;
+                return this;
+            }
+
+            /**
+             * @param radius the Radius for rounded corners to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withCornerRadius(float radius) {
+                this.cornerRadius = radius;
+                flags |= FLAG_CORNER_RADIUS;
+                return this;
+            }
+
+            /**
+             * @param visible The visibility to apply to the surface.
+             * @return this Builder
+             */
+            public Builder withVisibility(boolean visible) {
+                this.visible = visible;
+                flags |= FLAG_VISIBILITY;
+                return this;
+            }
+
+            /**
+             * @return a new SurfaceParams instance
+             */
+            public SurfaceParams build() {
+                return new SurfaceParams(surface, flags, alpha, matrix, windowCrop, layer,
+                        cornerRadius, visible);
+            }
+        }
+
+        private SurfaceParams(SurfaceControl surface, int params, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = params;
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
@@ -135,6 +233,36 @@
             this.visible = visible;
         }
 
+
+        /**
+         * Constructs surface parameters to be applied when the current view state gets pushed to
+         * RenderThread.
+         *
+         * @param surface The surface to modify.
+         * @param alpha Alpha to apply.
+         * @param matrix Matrix to apply.
+         * @param windowCrop Crop to apply.
+         * @param layer The layer to apply.
+         * @param cornerRadius The corner radius to apply.
+         * @param visible The visibility to apply.
+         *
+         * @deprecated Use {@link SurfaceParams.Builder} to create an instance.
+         */
+        @Deprecated
+        public SurfaceParams(SurfaceControl surface, float alpha, Matrix matrix,
+                Rect windowCrop, int layer, float cornerRadius, boolean visible) {
+            this.flags = FLAG_ALL;
+            this.surface = surface;
+            this.alpha = alpha;
+            this.matrix = new Matrix(matrix);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
+            this.layer = layer;
+            this.cornerRadius = cornerRadius;
+            this.visible = visible;
+        }
+
+        private final int flags;
+
         @VisibleForTesting
         public final SurfaceControl surface;
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6ce7120..cfb6a79a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4356,6 +4356,11 @@
     private Insets mLayoutInsets;
 
     /**
+     * Briefly describes the state of the view and is primarily used for accessibility support.
+     */
+    private CharSequence mStateDescription;
+
+    /**
      * Briefly describes the view and is primarily used for accessibility support.
      */
     private CharSequence mContentDescription;
@@ -5125,6 +5130,11 @@
     @Nullable
     private ContentCaptureSession mContentCaptureSession;
 
+    /**
+     * Whether {@link ContentCaptureSession} is cached, resets on {@link #invalidate()}.
+     */
+    private boolean mContentCaptureSessionCached;
+
     @LayoutRes
     private int mSourceLayoutId = ID_NULL;
 
@@ -5138,11 +5148,6 @@
     private int mExplicitStyle;
 
     /**
-     * Cached reference to the {@link ContentCaptureSession}, is reset on {@link #invalidate()}.
-     */
-    private ContentCaptureSession mCachedContentCaptureSession;
-
-    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -9560,18 +9565,21 @@
      */
     @Nullable
     public final ContentCaptureSession getContentCaptureSession() {
-        if (mCachedContentCaptureSession != null) {
-            return mCachedContentCaptureSession;
+        if (mContentCaptureSessionCached) {
+            return mContentCaptureSession;
         }
 
-        mCachedContentCaptureSession = getAndCacheContentCaptureSession();
-        return mCachedContentCaptureSession;
+        mContentCaptureSession = getAndCacheContentCaptureSession();
+        mContentCaptureSessionCached = true;
+        return mContentCaptureSession;
     }
 
     @Nullable
     private ContentCaptureSession getAndCacheContentCaptureSession() {
         // First try the session explicitly set by setContentCaptureSession()
-        if (mContentCaptureSession != null) return mContentCaptureSession;
+        if (mContentCaptureSession != null) {
+            return mContentCaptureSession;
+        }
 
         // Then the session explicitly set in an ancestor
         ContentCaptureSession session = null;
@@ -9922,6 +9930,7 @@
         info.setImportantForAccessibility(isImportantForAccessibility());
         info.setPackageName(mContext.getPackageName());
         info.setClassName(getAccessibilityClassName());
+        info.setStateDescription(getStateDescription());
         info.setContentDescription(getContentDescription());
 
         info.setEnabled(isEnabled());
@@ -10294,6 +10303,22 @@
     }
 
     /**
+     * Returns the {@link View}'s state description.
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the state description presented to accessibility services.
+     * You must call {@link #setStateDescription(CharSequence)} to modify the
+     * state description.
+     *
+     * @return the state description
+     * @see #setStateDescription(CharSequence)
+     */
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    public final @Nullable CharSequence getStateDescription() {
+        return mStateDescription;
+    }
+
+    /**
      * Returns the {@link View}'s content description.
      * <p>
      * <strong>Note:</strong> Do not override this method, as it will have no
@@ -10312,6 +10337,36 @@
     }
 
     /**
+     * Sets the {@link View}'s state description.
+     * <p>
+     * A state description briefly describes the states of the view and is primarily used
+     * for accessibility support to determine how the states of a view should be presented to
+     * the user. It is a supplement to the boolean states (for example, checked/unchecked) and
+     * it is used for customized state description (for example, "wifi, connected, three bars").
+     * State description changes frequently while content description should change less often.
+     *
+     * @param stateDescription The state description.
+     * @see #getStateDescription()
+     */
+    @RemotableViewMethod
+    public void setStateDescription(@Nullable CharSequence stateDescription) {
+        if (mStateDescription == null) {
+            if (stateDescription == null) {
+                return;
+            }
+        } else if (mStateDescription.equals(stateDescription)) {
+            return;
+        }
+        mStateDescription = stateDescription;
+        if (!TextUtils.isEmpty(stateDescription)
+                && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+        notifyViewAccessibilityStateChangedIfNeeded(
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
+    }
+
+    /**
      * Sets the {@link View}'s content description.
      * <p>
      * A content description briefly describes the view and is primarily used
@@ -14324,6 +14379,14 @@
     }
 
     /**
+     * @return true if this view and all ancestors are visible as of the last
+     * {@link #onVisibilityAggregated(boolean)} call.
+     */
+    boolean isAggregatedVisible() {
+        return (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+    }
+
+    /**
      * Internal dispatching method for {@link #onVisibilityAggregated}. Overridden by
      * ViewGroup. Intended to only be called when {@link #isAttachedToWindow()},
      * {@link #getWindowVisibility()} is {@link #VISIBLE} and this view's parent {@link #isShown()}.
@@ -14351,7 +14414,7 @@
     @CallSuper
     public void onVisibilityAggregated(boolean isVisible) {
         // Update our internal visibility tracking so we can detect changes
-        boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0;
+        boolean oldVisible = isAggregatedVisible();
         mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE)
                 : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE);
         if (isVisible && mAttachInfo != null) {
@@ -14403,7 +14466,9 @@
         }
 
         notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-        updateSystemGestureExclusionRects();
+        if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
+            postUpdateSystemGestureExclusionRects();
+        }
     }
 
     /**
@@ -18084,7 +18149,7 @@
 
         // Reset content capture caches
         mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
-        mCachedContentCaptureSession = null;
+        mContentCaptureSessionCached = false;
 
         if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index feba7bb..f25206d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -408,6 +408,7 @@
      *            or more of:
      *            <ul>
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
      *            <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 957673d..859e9a4 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -1138,6 +1138,12 @@
 
             boolean hardwareAccelerated = mView.isHardwareAccelerated();
 
+            // alpha requires slightly different treatment than the other (transform) properties.
+            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+            // logic is dependent on how the view handles an internal call to onSetAlpha().
+            // We track what kinds of properties are set, and how alpha is handled when it is
+            // set, and perform the invalidation steps appropriately.
+            boolean alphaHandled = false;
             if (!hardwareAccelerated) {
                 mView.invalidateParentCaches();
             }
@@ -1152,7 +1158,11 @@
                 for (int i = 0; i < count; ++i) {
                     NameValuesHolder values = valueList.get(i);
                     float value = values.mFromValue + fraction * values.mDeltaValue;
-                    setValue(values.mNameConstant, value);
+                    if (values.mNameConstant == ALPHA) {
+                        alphaHandled = mView.setAlphaNoInvalidation(value);
+                    } else {
+                        setValue(values.mNameConstant, value);
+                    }
                 }
             }
             if ((propertyMask & TRANSFORM_MASK) != 0) {
@@ -1160,8 +1170,13 @@
                     mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
                 }
             }
-
-            mView.invalidateViewProperty(false, false);
+            // invalidate(false) in all cases except if alphaHandled gets set to true
+            // via the call to setAlphaNoInvalidation(), above
+            if (alphaHandled) {
+                mView.invalidate(true);
+            } else {
+                mView.invalidateViewProperty(false, false);
+            }
             if (mUpdateListener != null) {
                 mUpdateListener.onAnimationUpdate(animation);
             }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3a51eaa..3756a7d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -468,7 +468,6 @@
     private final UnhandledKeyManager mUnhandledKeyManager = new UnhandledKeyManager();
 
     boolean mWindowAttributesChanged = false;
-    int mWindowAttributesChangesFlag = 0;
 
     // These can be accessed by any thread, must be protected with a lock.
     // Surface can never be reassigned or cleared (use Surface.clear()).
@@ -865,7 +864,6 @@
 
                 mSoftInputMode = attrs.softInputMode;
                 mWindowAttributesChanged = true;
-                mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
                 mAttachInfo.mRootView = view;
                 mAttachInfo.mScalingRequired = mTranslator != null;
                 mAttachInfo.mApplicationScale =
@@ -1269,14 +1267,12 @@
             attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
             attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
 
-            mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
-            if ((mWindowAttributesChangesFlag
-                    & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
+            final int changes = mWindowAttributes.copyFrom(attrs);
+            if ((changes & WindowManager.LayoutParams.TRANSLUCENT_FLAGS_CHANGED) != 0) {
                 // Recompute system ui visibility.
                 mAttachInfo.mRecomputeGlobalAttributes = true;
             }
-            if ((mWindowAttributesChangesFlag
-                    & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+            if ((changes & WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
                 // Request to update light center.
                 mAttachInfo.mNeedsUpdateLightCenter = true;
             }
@@ -2001,7 +1997,6 @@
         mIsInTraversal = true;
         mWillDrawSoon = true;
         boolean windowSizeMayChange = false;
-        final boolean windowAttributesChanged = mWindowAttributesChanged;
         WindowManager.LayoutParams lp = mWindowAttributes;
 
         int desiredWindowWidth;
@@ -2018,10 +2013,6 @@
                 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
 
         WindowManager.LayoutParams params = null;
-        if (mWindowAttributesChanged) {
-            mWindowAttributesChanged = false;
-            params = lp;
-        }
         CompatibilityInfo compatibilityInfo =
                 mDisplay.getDisplayAdjustments().getCompatibilityInfo();
         if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
@@ -2037,8 +2028,6 @@
             }
         }
 
-        mWindowAttributesChangesFlag = 0;
-
         Rect frame = mWinFrame;
         if (mFirst) {
             mFullRedrawNeeded = true;
@@ -2200,16 +2189,6 @@
             }
         }
 
-        if (params != null) {
-            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
-                if (!PixelFormat.formatHasAlpha(params.format)) {
-                    params.format = PixelFormat.TRANSLUCENT;
-                }
-            }
-            mAttachInfo.mOverscanRequested = (params.flags
-                    & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
-        }
-
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
             mLastOverscanRequested = mAttachInfo.mOverscanRequested;
@@ -2265,6 +2244,21 @@
         /* True if surface generation id changes. */
         boolean surfaceReplaced = false;
 
+        final boolean windowAttributesChanged = mWindowAttributesChanged;
+        if (windowAttributesChanged) {
+            mWindowAttributesChanged = false;
+            params = lp;
+        }
+
+        if (params != null) {
+            if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
+                    && !PixelFormat.formatHasAlpha(params.format)) {
+                params.format = PixelFormat.TRANSLUCENT;
+            }
+            mAttachInfo.mOverscanRequested =
+                    (params.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
+        }
+
         if (mFirst || windowShouldResize || insetsChanged ||
                 viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
             mForceNextWindowRelayout = false;
@@ -3313,14 +3307,19 @@
     public void requestTransparentRegion(View child) {
         // the test below should not fail unless someone is messing with us
         checkThread();
-        if (mView == child) {
+        if (mView != child) {
+            return;
+        }
+
+        if ((mView.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
             mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
             // Need to make sure we re-evaluate the window attributes next
             // time around, to ensure the window has the correct format.
             mWindowAttributesChanged = true;
-            mWindowAttributesChangesFlag = 0;
-            requestLayout();
         }
+
+        // Always request layout to apply the latest transparent region.
+        requestLayout();
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index b708323..396422e 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -18,9 +18,14 @@
 
 import static android.view.WindowInsets.Type.ime;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.graphics.Insets;
 import android.view.WindowInsets.Type.InsetType;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Interface to control windows that generate insets.
  *
@@ -30,6 +35,73 @@
 public interface WindowInsetsController {
 
     /**
+     * Makes system bars become opaque with solid dark background and light foreground.
+     * @hide
+     */
+    int APPEARANCE_OPAQUE_BARS = 1;
+
+    /**
+     * Makes items on system bars become less noticeable without changing the layout of the bars.
+     * @hide
+     */
+    int APPEARANCE_LOW_PROFILE_BARS = 1 << 1;
+
+    /**
+     * Changes the foreground color for the light top bar so that the items on the bar can be read
+     * clearly.
+     */
+    int APPEARANCE_LIGHT_TOP_BAR = 1 << 2;
+
+    /**
+     * Changes the foreground color for the light side bars so that the items on the bar can be read
+     * clearly.
+     */
+    int APPEARANCE_LIGHT_SIDE_BARS = 1 << 3;
+
+    /** Determines the appearance of system bars. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {APPEARANCE_OPAQUE_BARS, APPEARANCE_LOW_PROFILE_BARS,
+            APPEARANCE_LIGHT_TOP_BAR, APPEARANCE_LIGHT_SIDE_BARS})
+    @interface Appearance {
+    }
+
+    /**
+     * The default option for {@link #setSystemBarsBehavior(int)}. The side bars will be forcibly
+     * shown by the system on any user interaction on the corresponding display if the side bars are
+     * hidden by {@link #hide(int)} or {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     */
+    int BEHAVIOR_SHOW_SIDE_BARS_BY_TOUCH = 0;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding the side bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed with system gestures, such
+     * as swiping from the edge of the screen where the bar is hidden from.</p>
+     */
+    int BEHAVIOR_SHOW_BARS_BY_SWIPE = 1;
+
+    /**
+     * Option for {@link #setSystemBarsBehavior(int)}: Window would like to remain interactive when
+     * hiding the side bars by calling {@link #hide(int)} or
+     * {@link WindowInsetsAnimationController#changeInsets(Insets)}.
+     *
+     * <p>When system bars are hidden in this mode, they can be revealed temporarily with system
+     * gestures, such as swiping from the edge of the screen where the bar is hidden from. These
+     * transient system bars will overlay app’s content, may have some degree of transparency, and
+     * will automatically hide after a short timeout.</p>
+     */
+    int BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE = 2;
+
+    /** Determines the behavior of system bars when hiding them by calling {@link #hide}. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {BEHAVIOR_SHOW_SIDE_BARS_BY_TOUCH, BEHAVIOR_SHOW_BARS_BY_SWIPE,
+            BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE})
+    @interface Behavior {
+    }
+
+    /**
      * Makes a set of windows that cause insets appear on screen.
      * <p>
      * Note that if the window currently doesn't have control over a certain type, it will apply the
@@ -108,4 +180,20 @@
     default void hideInputMethod() {
         hide(ime());
     }
+
+    /**
+     * Controls the appearance of system bars.
+     *
+     * @param appearance Bitmask of {@link Appearance} flags.
+     * @see Appearance
+     */
+    void setSystemBarsAppearance(@Appearance int appearance);
+
+    /**
+     * Controls the behavior of system bars.
+     *
+     * @param behavior Determines how the bars behave when being hidden by the application.
+     * @see Behavior
+     */
+    void setSystemBarsBehavior(@Behavior int behavior);
 }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 2e5a750..001ab66 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
 import static android.view.WindowLayoutParamsProto.ALPHA;
+import static android.view.WindowLayoutParamsProto.APPEARANCE;
+import static android.view.WindowLayoutParamsProto.BEHAVIOR;
 import static android.view.WindowLayoutParamsProto.BUTTON_BRIGHTNESS;
 import static android.view.WindowLayoutParamsProto.COLOR_MODE;
 import static android.view.WindowLayoutParamsProto.FLAGS;
@@ -1473,6 +1475,9 @@
          * <p>When this flag is enabled for a window, it automatically sets
          * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
          * {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag may be ignored.
          */
         public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
 
@@ -1492,6 +1497,10 @@
          * <p>When this flag is enabled for a window, it automatically sets
          * the system UI visibility flags {@link View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
          * {@link View#SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}.</p>
+         *
+         * <p>Note: For devices that support
+         * {@link android.content.pm.PackageManager#FEATURE_AUTOMOTIVE} this flag can be disabled
+         * by the car manufacturers.
          */
         public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
 
@@ -2612,6 +2621,14 @@
         @ActivityInfo.ColorMode
         private int mColorMode = COLOR_MODE_DEFAULT;
 
+        /**
+         * Carries the requests about {@link WindowInsetsController.Appearance} and
+         * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
+         *
+         * @hide
+         */
+        public final InsetsFlags insetsFlags = new InsetsFlags();
+
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
             type = TYPE_APPLICATION;
@@ -2774,6 +2791,8 @@
             TextUtils.writeToParcel(accessibilityTitle, out, parcelableFlags);
             out.writeInt(mColorMode);
             out.writeLong(hideTimeoutMilliseconds);
+            out.writeInt(insetsFlags.appearance);
+            out.writeInt(insetsFlags.behavior);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -2830,6 +2849,8 @@
             accessibilityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             mColorMode = in.readInt();
             hideTimeoutMilliseconds = in.readLong();
+            insetsFlags.appearance = in.readInt();
+            insetsFlags.behavior = in.readInt();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -2876,7 +2897,7 @@
         /** {@hide} */
         public static final int COLOR_MODE_CHANGED = 1 << 26;
         /** {@hide} */
-        public static final int EVERYTHING_CHANGED = 0xffffffff;
+        public static final int INSET_FLAGS_CHANGED = 1 << 27;
 
         // internal buffer to backup/restore parameters under compatibility mode.
         private int[] mCompatibilityParamsBackup = null;
@@ -3065,6 +3086,16 @@
             // This can't change, it's only set at window creation time.
             hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
 
+            if (insetsFlags.appearance != o.insetsFlags.appearance) {
+                insetsFlags.appearance = o.insetsFlags.appearance;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
+            if (insetsFlags.behavior != o.insetsFlags.behavior) {
+                insetsFlags.behavior = o.insetsFlags.behavior;
+                changes |= INSET_FLAGS_CHANGED;
+            }
+
             return changes;
         }
 
@@ -3212,6 +3243,17 @@
                 sb.append(prefix).append("  vsysui=").append(ViewDebug.flagsToString(
                         View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
             }
+            if (insetsFlags.appearance != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  apr=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "appearance", insetsFlags.appearance));
+            }
+            if (insetsFlags.behavior != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  bhv=").append(ViewDebug.flagsToString(
+                        InsetsFlags.class, "behavior", insetsFlags.behavior));
+            }
+
             sb.append('}');
             return sb.toString();
         }
@@ -3247,6 +3289,8 @@
             proto.write(PRIVATE_FLAGS, privateFlags);
             proto.write(SYSTEM_UI_VISIBILITY_FLAGS, systemUiVisibility);
             proto.write(SUBTREE_SYSTEM_UI_VISIBILITY_FLAGS, subtreeSystemUiVisibility);
+            proto.write(APPEARANCE, insetsFlags.appearance);
+            proto.write(BEHAVIOR, insetsFlags.behavior);
             proto.end(token);
         }
 
diff --git a/core/java/android/view/WindowlessViewRoot.java b/core/java/android/view/WindowlessViewRoot.java
index 75057a2..b76e1fa 100644
--- a/core/java/android/view/WindowlessViewRoot.java
+++ b/core/java/android/view/WindowlessViewRoot.java
@@ -21,12 +21,15 @@
 import android.view.SurfaceControl;
 import android.view.View;
 
+import android.annotation.TestApi;
+
 /**
  * Utility class for adding a view hierarchy to a SurfaceControl.
  *
  * See WindowlessWmTest for example usage.
  * @hide
  */
+@TestApi
 public class WindowlessViewRoot {
     ViewRootImpl mViewRoot;
     WindowlessWindowManager mWm;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 5844365..430fb6d 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.res.Configuration;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -93,6 +94,15 @@
     @Override
     public void remove(android.view.IWindow window) {}
 
+    private boolean isOpaque(WindowManager.LayoutParams attrs) {
+        if (attrs.surfaceInsets != null && attrs.surfaceInsets.left != 0 || 
+                attrs.surfaceInsets.top != 0 || attrs.surfaceInsets.right != 0 ||
+                attrs.surfaceInsets.bottom != 0) {
+            return false;
+        }
+        return !PixelFormat.formatHasAlpha(attrs.format);
+    }
+
     @Override
     public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
             int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
@@ -109,7 +119,17 @@
                     "Invalid window token (never added or removed already)");
         }
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        t.show(sc).setBufferSize(sc, requestedWidth, requestedHeight).apply();
+
+        final Rect surfaceInsets = attrs.surfaceInsets;
+        int width = surfaceInsets != null ?
+                requestedWidth + surfaceInsets.left + surfaceInsets.right : requestedWidth;
+        int height = surfaceInsets != null ?
+                requestedHeight + surfaceInsets.top + surfaceInsets.bottom : requestedHeight;
+
+        t.show(sc)
+            .setBufferSize(sc, width, height)
+            .setOpaque(sc, isOpaque(attrs))
+            .apply();
         outSurfaceControl.copyFrom(sc);
         outFrame.set(0, 0, requestedWidth, requestedHeight);
 
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 484d9a1..dc8bf9b 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -71,7 +71,9 @@
 
     private boolean mIsAllWindowsCached;
 
-    private final SparseArray<AccessibilityWindowInfo> mWindowCache =
+    // The SparseArray of all {@link AccessibilityWindowInfo}s on all displays.
+    // The key of outer SparseArray is display ID and the key of inner SparseArray is window ID.
+    private final SparseArray<SparseArray<AccessibilityWindowInfo>> mWindowCacheByDisplay =
             new SparseArray<>();
 
     private final SparseArray<LongSparseArray<AccessibilityNodeInfo>> mNodeCache =
@@ -84,34 +86,66 @@
         mAccessibilityNodeRefresher = nodeRefresher;
     }
 
-    public void setWindows(List<AccessibilityWindowInfo> windows) {
+    /**
+     * Sets all {@link AccessibilityWindowInfo}s of all displays into the cache.
+     * The key of SparseArray is display ID.
+     *
+     * @param windowsOnAllDisplays The accessibility windows of all displays.
+     */
+    public void setWindowsOnAllDisplays(
+            SparseArray<List<AccessibilityWindowInfo>> windowsOnAllDisplays) {
         synchronized (mLock) {
             if (DEBUG) {
                 Log.i(LOG_TAG, "Set windows");
             }
-            clearWindowCache();
-            if (windows == null) {
+            clearWindowCacheLocked();
+            if (windowsOnAllDisplays == null) {
                 return;
             }
-            final int windowCount = windows.size();
-            for (int i = 0; i < windowCount; i++) {
-                final AccessibilityWindowInfo window = windows.get(i);
-                addWindow(window);
+
+            final int displayCounts = windowsOnAllDisplays.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final List<AccessibilityWindowInfo> windowsOfDisplay =
+                        windowsOnAllDisplays.valueAt(i);
+
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                final int displayId = windowsOnAllDisplays.keyAt(i);
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    addWindowByDisplayLocked(displayId, windowsOfDisplay.get(j));
+                }
             }
             mIsAllWindowsCached = true;
         }
     }
 
+    /**
+     * Sets an {@link AccessibilityWindowInfo} into the cache.
+     *
+     * @param window The accessibility window.
+     */
     public void addWindow(AccessibilityWindowInfo window) {
         synchronized (mLock) {
             if (DEBUG) {
-                Log.i(LOG_TAG, "Caching window: " + window.getId());
+                Log.i(LOG_TAG, "Caching window: " + window.getId() + " at display Id [ "
+                        + window.getDisplayId() + " ]");
             }
-            final int windowId = window.getId();
-            mWindowCache.put(windowId, new AccessibilityWindowInfo(window));
+            addWindowByDisplayLocked(window.getDisplayId(), window);
         }
     }
 
+    private void addWindowByDisplayLocked(int displayId, AccessibilityWindowInfo window) {
+        SparseArray<AccessibilityWindowInfo> windows = mWindowCacheByDisplay.get(displayId);
+        if (windows == null) {
+            windows = new SparseArray<>();
+            mWindowCacheByDisplay.put(displayId, windows);
+        }
+        final int windowId = window.getId();
+        windows.put(windowId, new AccessibilityWindowInfo(window));
+    }
     /**
      * Notifies the cache that the something in the UI changed. As a result
      * the cache will either refresh some nodes or evict some nodes.
@@ -236,44 +270,82 @@
         }
     }
 
-    public List<AccessibilityWindowInfo> getWindows() {
+    /**
+     * Gets all {@link AccessibilityWindowInfo}s of all displays from the cache.
+     *
+     * @return All cached {@link AccessibilityWindowInfo}s of all displays
+     *         or null if such not found. The key of SparseArray is display ID.
+     */
+    public SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
         synchronized (mLock) {
             if (!mIsAllWindowsCached) {
                 return null;
             }
+            final SparseArray<List<AccessibilityWindowInfo>> returnWindows = new SparseArray<>();
+            final int displayCounts = mWindowCacheByDisplay.size();
 
-            final int windowCount = mWindowCache.size();
-            if (windowCount > 0) {
-                // Careful to return the windows in a decreasing layer order.
-                SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
-                sortedWindows.clear();
+            if (displayCounts > 0) {
+                for (int i = 0; i < displayCounts; i++) {
+                    final int displayId = mWindowCacheByDisplay.keyAt(i);
+                    final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                            mWindowCacheByDisplay.valueAt(i);
 
-                for (int i = 0; i < windowCount; i++) {
-                    AccessibilityWindowInfo window = mWindowCache.valueAt(i);
-                    sortedWindows.put(window.getLayer(), window);
+                    if (windowsOfDisplay == null) {
+                        continue;
+                    }
+
+                    final int windowCount = windowsOfDisplay.size();
+                    if (windowCount > 0) {
+                        // Careful to return the windows in a decreasing layer order.
+                        SparseArray<AccessibilityWindowInfo> sortedWindows = mTempWindowArray;
+                        sortedWindows.clear();
+
+                        for (int j = 0; j < windowCount; j++) {
+                            AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+                            sortedWindows.put(window.getLayer(), window);
+                        }
+
+                        // It's possible in transient conditions for two windows to share the same
+                        // layer, which results in sortedWindows being smaller than
+                        // mWindowCacheByDisplay
+                        final int sortedWindowCount = sortedWindows.size();
+                        List<AccessibilityWindowInfo> windows =
+                                new ArrayList<>(sortedWindowCount);
+                        for (int j = sortedWindowCount - 1; j >= 0; j--) {
+                            AccessibilityWindowInfo window = sortedWindows.valueAt(j);
+                            windows.add(new AccessibilityWindowInfo(window));
+                            sortedWindows.removeAt(j);
+                        }
+                        returnWindows.put(displayId, windows);
+                    }
                 }
-
-                // It's possible in transient conditions for two windows to share the same
-                // layer, which results in sortedWindows being smaller than mWindowCache
-                final int sortedWindowCount = sortedWindows.size();
-                List<AccessibilityWindowInfo> windows = new ArrayList<>(sortedWindowCount);
-                for (int i = sortedWindowCount - 1; i >= 0; i--) {
-                    AccessibilityWindowInfo window = sortedWindows.valueAt(i);
-                    windows.add(new AccessibilityWindowInfo(window));
-                    sortedWindows.removeAt(i);
-                }
-
-                return windows;
+                return returnWindows;
             }
             return null;
         }
     }
 
+    /**
+     * Gets an {@link AccessibilityWindowInfo} by windowId.
+     *
+     * @param windowId The id of the window.
+     *
+     * @return The {@link AccessibilityWindowInfo} or null if such not found.
+     */
     public AccessibilityWindowInfo getWindow(int windowId) {
         synchronized (mLock) {
-            AccessibilityWindowInfo window = mWindowCache.get(windowId);
-            if (window != null) {
-                return new AccessibilityWindowInfo(window);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
+                if (windowsOfDisplay == null) {
+                    continue;
+                }
+
+                AccessibilityWindowInfo window = windowsOfDisplay.get(windowId);
+                if (window != null) {
+                    return new AccessibilityWindowInfo(window);
+                }
             }
             return null;
         }
@@ -358,7 +430,7 @@
             if (DEBUG) {
                 Log.i(LOG_TAG, "clear()");
             }
-            clearWindowCache();
+            clearWindowCacheLocked();
             final int nodesForWindowCount = mNodeCache.size();
             for (int i = nodesForWindowCount - 1; i >= 0; i--) {
                 final int windowId = mNodeCache.keyAt(i);
@@ -370,8 +442,23 @@
         }
     }
 
-    private void clearWindowCache() {
-        mWindowCache.clear();
+    private void clearWindowCacheLocked() {
+        if (DEBUG) {
+            Log.i(LOG_TAG, "clearWindowCacheLocked");
+        }
+        final int displayCounts = mWindowCacheByDisplay.size();
+
+        if (displayCounts > 0) {
+            for (int i = displayCounts - 1; i >= 0; i--) {
+                final int displayId = mWindowCacheByDisplay.keyAt(i);
+                final SparseArray<AccessibilityWindowInfo> windows =
+                        mWindowCacheByDisplay.get(displayId);
+                if (windows != null) {
+                    windows.clear();
+                }
+                mWindowCacheByDisplay.remove(displayId);
+            }
+        }
         mIsAllWindowsCached = false;
     }
 
@@ -444,32 +531,41 @@
     public void checkIntegrity() {
         synchronized (mLock) {
             // Get the root.
-            if (mWindowCache.size() <= 0 && mNodeCache.size() == 0) {
+            if (mWindowCacheByDisplay.size() <= 0 && mNodeCache.size() == 0) {
                 return;
             }
 
             AccessibilityWindowInfo focusedWindow = null;
             AccessibilityWindowInfo activeWindow = null;
 
-            final int windowCount = mWindowCache.size();
-            for (int i = 0; i < windowCount; i++) {
-                AccessibilityWindowInfo window = mWindowCache.valueAt(i);
+            final int displayCounts = mWindowCacheByDisplay.size();
+            for (int i = 0; i < displayCounts; i++) {
+                final SparseArray<AccessibilityWindowInfo> windowsOfDisplay =
+                        mWindowCacheByDisplay.valueAt(i);
 
-                // Check for one active window.
-                if (window.isActive()) {
-                    if (activeWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate active window:" + window);
-                    } else {
-                        activeWindow = window;
-                    }
+                if (windowsOfDisplay == null) {
+                    continue;
                 }
 
-                // Check for one focused window.
-                if (window.isFocused()) {
-                    if (focusedWindow != null) {
-                        Log.e(LOG_TAG, "Duplicate focused window:" + window);
-                    } else {
-                        focusedWindow = window;
+                final int windowCount = windowsOfDisplay.size();
+                for (int j = 0; j < windowCount; j++) {
+                    final AccessibilityWindowInfo window = windowsOfDisplay.valueAt(j);
+
+                    // Check for one active window.
+                    if (window.isActive()) {
+                        if (activeWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate active window:" + window);
+                        } else {
+                            activeWindow = window;
+                        }
+                    }
+                    // Check for one focused window.
+                    if (window.isFocused()) {
+                        if (focusedWindow != null) {
+                            Log.e(LOG_TAG, "Duplicate focused window:" + window);
+                        } else {
+                            focusedWindow = window;
+                        }
                     }
                 }
             }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index fd09a87..32b0f41 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -602,6 +602,13 @@
     public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020;
 
     /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * state description of the node as returned by
+     * {@link AccessibilityNodeInfo#getStateDescription} changed.
+     */
+    public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 0x00000040;
+
+    /**
      * Change type for {@link #TYPE_WINDOWS_CHANGED} event:
      * The window was added.
      */
@@ -696,6 +703,7 @@
                     CONTENT_CHANGE_TYPE_SUBTREE,
                     CONTENT_CHANGE_TYPE_TEXT,
                     CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+                    CONTENT_CHANGE_TYPE_STATE_DESCRIPTION,
                     CONTENT_CHANGE_TYPE_PANE_TITLE,
                     CONTENT_CHANGE_TYPE_PANE_APPEARED,
                     CONTENT_CHANGE_TYPE_PANE_DISAPPEARED
@@ -885,6 +893,7 @@
      * @return The bit mask of change types. One or more of:
      *         <ul>
      *         <li>{@link #CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link #CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *         <li>{@link #CONTENT_CHANGE_TYPE_SUBTREE}
      *         <li>{@link #CONTENT_CHANGE_TYPE_TEXT}
      *         <li>{@link #CONTENT_CHANGE_TYPE_PANE_TITLE}
@@ -906,6 +915,8 @@
         switch (type) {
             case CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION:
                 return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION";
+            case CONTENT_CHANGE_TYPE_STATE_DESCRIPTION:
+                return "CONTENT_CHANGE_TYPE_STATE_DESCRIPTION";
             case CONTENT_CHANGE_TYPE_SUBTREE: return "CONTENT_CHANGE_TYPE_SUBTREE";
             case CONTENT_CHANGE_TYPE_TEXT: return "CONTENT_CHANGE_TYPE_TEXT";
             case CONTENT_CHANGE_TYPE_PANE_TITLE: return "CONTENT_CHANGE_TYPE_PANE_TITLE";
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 4db6f4f..d9fa9f2 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
+import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
@@ -267,12 +268,14 @@
         try {
             IAccessibilityServiceConnection connection = getConnection(connectionId);
             if (connection != null) {
-                List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows();
-                if (windows != null) {
+                SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                        sAccessibilityCache.getWindowsOnAllDisplays();
+                List<AccessibilityWindowInfo> windows;
+                if (allWindows != null) {
                     if (DEBUG) {
                         Log.i(LOG_TAG, "Windows cache hit");
                     }
-                    return windows;
+                    return allWindows.valueAt(Display.DEFAULT_DISPLAY);
                 }
                 if (DEBUG) {
                     Log.i(LOG_TAG, "Windows cache miss");
@@ -284,7 +287,9 @@
                     Binder.restoreCallingIdentity(identityToken);
                 }
                 if (windows != null) {
-                    sAccessibilityCache.setWindows(windows);
+                    allWindows = new SparseArray<>();
+                    allWindows.put(Display.DEFAULT_DISPLAY, windows);
+                    sAccessibilityCache.setWindowsOnAllDisplays(allWindows);
                     return windows;
                 }
             } else {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 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/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/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d302c2b..032af1c 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -707,9 +707,10 @@
                         if (mBindSequence != bindSequence) {
                             return;
                         }
-                        if (matrixValues == null) {
-                            // That this app is unbound from the parent ActivityView. In this case,
-                            // calling updateCursorAnchorInfo() isn't safe.  Only clear the matrix.
+                        if (matrixValues == null || mActivityViewToScreenMatrix == null) {
+                            // Either InputBoundResult#mActivityViewToScreenMatrixValues is null
+                            // OR this app is unbound from the parent ActivityView. In this case,
+                            // calling updateCursorAnchorInfo() isn't safe. Only clear the matrix.
                             mActivityViewToScreenMatrix = null;
                             return;
                         }
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 0473f54..4554fdc 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -1,3 +1,3 @@
 alanv@google.com
-ashleyrose@google.com
-aurimas@google.com
\ No newline at end of file
+aurimas@google.com
+emberr@google.com
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cac75cfd..57ce28e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2561,7 +2561,8 @@
      * @return True when the TextView isFocused and has a valid zero-length selection (cursor).
      */
     private boolean shouldBlink() {
-        if (!isCursorVisible() || !mTextView.isFocused()) return false;
+        if (!isCursorVisible() || !mTextView.isFocused() ||
+                !mTextView.isVisibleToUser()) return false;
 
         final int start = mTextView.getSelectionStart();
         if (start < 0) return false;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 2f44d6e..1cb1148 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -4109,7 +4109,7 @@
         final int rowsCount = getCount();
         final int selectionMode = getSelectionModeForAccessibility();
         final CollectionInfo collectionInfo = CollectionInfo.obtain(
-                rowsCount, 1, false, selectionMode);
+                -1, -1, false, selectionMode);
         info.setCollectionInfo(collectionInfo);
 
         if (rowsCount > 0) {
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index d3c6972..f58b6d1 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -800,6 +800,7 @@
         // The surface we allocate for the magnifier content + shadow.
         private final SurfaceSession mSurfaceSession;
         private final SurfaceControl mSurfaceControl;
+        private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
         private final Surface mSurface;
         // The renderer used for the allocated surface.
         private final ThreadedRenderer.SimpleRenderer mRenderer;
@@ -1081,16 +1082,16 @@
                             return;
                         }
                         // Show or move the window at the content draw frame.
-                        SurfaceControl.openTransaction();
-                        mSurfaceControl.deferTransactionUntil(mSurface, frame);
+                        mTransaction.deferTransactionUntilSurface(mSurfaceControl, mSurface, frame);
                         if (updateWindowPosition) {
-                            mSurfaceControl.setPosition(pendingX, pendingY);
+                            mTransaction.setPosition(mSurfaceControl, pendingX, pendingY);
                         }
                         if (firstDraw) {
-                            mSurfaceControl.setLayer(SURFACE_Z);
-                            mSurfaceControl.show();
+                            mTransaction.setLayer(mSurfaceControl, SURFACE_Z)
+                                .show(mSurfaceControl);
+
                         }
-                        SurfaceControl.closeTransaction();
+                        mTransaction.apply();
                     };
                     mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
                 } else {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index eb93fdf..1cc8ff6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -71,9 +71,9 @@
  * instead of scroll view which offers greater user interface flexibility and
  * support for the material design scrolling patterns.</p>
  *
- * <p>To learn more about material design patterns for handling scrolling, see
- * <a href="https://material.io/guidelines/patterns/scrolling-techniques.html#">
- * Scrolling techniques</a>.</p>
+ * <p>Material Design offers guidelines on how the appearance of
+ * <a href="https://material.io/components/">several UI components</a>, including app bars and
+ * banners, should respond to gestures.</p>
  *
  * @attr ref android.R.styleable#ScrollView_fillViewport
  */
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index faeecda..cae1f38 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -24,6 +24,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -120,6 +121,7 @@
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -178,6 +180,20 @@
     private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
     private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
 
+    public static final int TARGET_TYPE_DEFAULT = 0;
+    public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
+    public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
+    public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+
+    @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
+            TARGET_TYPE_DEFAULT,
+            TARGET_TYPE_CHOOSER_TARGET,
+            TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
+            TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShareTargetType {}
+
     /**
      * The transition time between placeholders for direct share to a message
      * indicating that non are available.
@@ -218,9 +234,9 @@
     private int mCurrAvailableWidth = 0;
 
     /** {@link ChooserActivity#getBaseScore} */
-    private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
+    public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
     /** {@link ChooserActivity#getBaseScore} */
-    private static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
+    public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
     // TODO: Update to handle landscape instead of using static value
     private static final int MAX_RANKED_TARGETS = 4;
@@ -443,7 +459,7 @@
                     }
                     if (sri.resultTargets != null) {
                         mChooserListAdapter.addServiceResults(sri.originalTarget,
-                                sri.resultTargets, false);
+                                sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET);
                     }
                     unbindService(sri.connection);
                     sri.connection.destroy();
@@ -474,7 +490,7 @@
                     final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
                     if (resultInfo.resultTargets != null) {
                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
-                                resultInfo.resultTargets, true);
+                                resultInfo.resultTargets, msg.arg1);
                     }
                     break;
 
@@ -493,6 +509,9 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         final long intentReceivedTime = System.currentTimeMillis();
+        // This is the only place this value is being set. Effectively final.
+        mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
+
         mIsSuccessfullySelected = false;
         Intent intent = getIntent();
         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
@@ -619,9 +638,6 @@
                 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
                 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
 
-        // This is the only place this value is being set. Effectively final.
-        mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
-
         AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
         if (appPredictor != null) {
             mDirectShareAppTargetCache = new HashMap<>();
@@ -714,7 +730,13 @@
     /**
      * Returns true if app prediction service is defined and the component exists on device.
      */
-    private boolean isAppPredictionServiceAvailable() {
+    @VisibleForTesting
+    public boolean isAppPredictionServiceAvailable() {
+        if (getPackageManager().getAppPredictionServicePackageName() == null) {
+            // Default AppPredictionService is not defined.
+            return false;
+        }
+
         final String appPredictionServiceName =
                 getString(R.string.config_defaultAppPredictionService);
         if (appPredictionServiceName == null) {
@@ -797,6 +819,11 @@
             clipboardManager.setPrimaryClip(clipData);
             Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
 
+            // Log share completion via copy
+            LogMaker targetLogMaker = new LogMaker(
+                    MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
+            getMetricsLogger().write(targetLogMaker);
+
             finish();
         }
     }
@@ -1209,7 +1236,7 @@
         mChooserListAdapter = (ChooserListAdapter) adapter;
         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
             mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
-                    false);
+                    TARGET_TYPE_DEFAULT);
         }
         mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
         if (listView != null) {
@@ -1555,33 +1582,32 @@
             }
         }
 
+        // If |appTargets| is not null, results are from AppPredictionService and already sorted.
+        final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
+                TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+
         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
         // for direct share targets. After ShareSheet is refactored we should use the
         // ShareShortcutInfos directly.
         boolean resultMessageSent = false;
         for (int i = 0; i < driList.size(); i++) {
-            List<ChooserTarget> chooserTargets = new ArrayList<>();
+            List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
             for (int j = 0; j < resultList.size(); j++) {
                 if (driList.get(i).getResolvedComponentName().equals(
                             resultList.get(j).getTargetComponent())) {
-                    ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
-                    // Incoming results are ordered but without a score. Create a score
-                    // based on the index in order to be sorted appropriately when joined
-                    // with legacy direct share api results.
-                    float score = Math.max(1.0f - (0.05f * j), 0.0f);
-                    ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
-                    chooserTargets.add(chooserTarget);
-                    if (mDirectShareAppTargetCache != null && appTargets != null) {
-                        mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
-                    }
+                    matchingShortcuts.add(resultList.get(j));
                 }
             }
-            if (chooserTargets.isEmpty()) {
+            if (matchingShortcuts.isEmpty()) {
                 continue;
             }
+            List<ChooserTarget> chooserTargets = convertToChooserTarget(
+                    matchingShortcuts, resultList, appTargets, shortcutType);
+
             final Message msg = Message.obtain();
             msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
             msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
+            msg.arg1 = shortcutType;
             mChooserHandler.sendMessage(msg);
             resultMessageSent = true;
         }
@@ -1615,23 +1641,69 @@
         return false;
     }
 
-    private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
-                                                 float score) {
-        ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
-        Bundle extras = new Bundle();
-        extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
-        return new ChooserTarget(
-                // The name of this target.
-                shortcutInfo.getShortLabel(),
-                // Don't load the icon until it is selected to be shown
-                null,
-                // The ranking score for this target (0.0-1.0); the system will omit items with low
-                // scores when there are too many Direct Share items.
-                score,
-                // The name of the component to be launched if this target is chosen.
-                shareShortcut.getTargetComponent().clone(),
-                // The extra values here will be merged into the Intent when this target is chosen.
-                extras);
+    /**
+     * Converts a list of ShareShortcutInfos to ChooserTargets.
+     * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
+     *                         share intent filter.
+     * @param allShortcuts List of all the shortcuts from all the packages on the device that are
+     *                    returned for the current sharing action.
+     * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
+     * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
+     *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
+     * @return A list of ChooserTargets sorted by score in descending order.
+     */
+    @VisibleForTesting
+    @NonNull
+    public List<ChooserTarget> convertToChooserTarget(
+            @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
+            @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
+            @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
+        // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
+        // list instead of the actual rank value when converting a rank to a score.
+        List<Integer> scoreList = new ArrayList<>();
+        if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+            for (int i = 0; i < matchingShortcuts.size(); i++) {
+                int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
+                if (!scoreList.contains(shortcutRank)) {
+                    scoreList.add(shortcutRank);
+                }
+            }
+            Collections.sort(scoreList);
+        }
+
+        List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
+        for (int i = 0; i < matchingShortcuts.size(); i++) {
+            ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
+            int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
+
+            float score;
+            if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
+                // Incoming results are ordered. Create a score based on index in the original list.
+                score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
+            } else {
+                // Create a score based on the rank of the shortcut.
+                int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
+                score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
+            }
+
+            Bundle extras = new Bundle();
+            extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
+            ChooserTarget chooserTarget = new ChooserTarget(shortcutInfo.getShortLabel(),
+                    null, // Icon will be loaded later if this target is selected to be shown.
+                    score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
+
+            chooserTargetList.add(chooserTarget);
+            if (mDirectShareAppTargetCache != null && allAppTargets != null) {
+                mDirectShareAppTargetCache.put(chooserTarget,
+                        allAppTargets.get(indexInAllShortcuts));
+            }
+        }
+
+        // Sort ChooserTargets by score in descending order
+        Comparator<ChooserTarget> byScore =
+                (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
+        Collections.sort(chooserTargetList, byScore);
+        return chooserTargetList;
     }
 
     private String convertServiceName(String packageName, String serviceName) {
@@ -1666,10 +1738,6 @@
         mServiceConnections.clear();
     }
 
-    public void onSetupVoiceInteraction() {
-        // Do nothing. We'll send the voice stuff ourselves.
-    }
-
     private void logDirectShareTargetReceived(int logCategory) {
         final long queryTime =
                 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
@@ -1727,8 +1795,7 @@
         if (!mIsAppPredictorComponentAvailable) {
             return null;
         }
-        if (mAppPredictor == null
-                    && getPackageManager().getAppPredictionServicePackageName() != null) {
+        if (mAppPredictor == null) {
             final IntentFilter filter = getTargetIntentFilter();
             Bundle extras = new Bundle();
             extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
@@ -2676,7 +2743,7 @@
          * if score is too low.
          */
         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
-                boolean isShortcutResult) {
+                @ShareTargetType int targetType) {
             if (DEBUG) {
                 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
                         + " targets");
@@ -2686,9 +2753,12 @@
                 return;
             }
 
-            final float baseScore = getBaseScore(origTarget, isShortcutResult);
+            final float baseScore = getBaseScore(origTarget, targetType);
             Collections.sort(targets, mBaseTargetComparator);
 
+            final boolean isShortcutResult =
+                    (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+                            || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
             final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
                                        : MAX_CHOOSER_TARGETS_PER_APP;
             float lastScore = 0;
@@ -2739,17 +2809,17 @@
           *   <li>Legacy direct share targets
           * </ol>
           */
-        private float getBaseScore(DisplayResolveInfo target, boolean isShortcutResult) {
+        public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
             if (target == null) {
                 return CALLER_TARGET_SCORE_BOOST;
             }
 
-            if (isShortcutResult && getAppPredictorForDirectShareIfEnabled() != null) {
+            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
                 return SHORTCUT_TARGET_SCORE_BOOST;
             }
 
             float score = super.getScore(target);
-            if (isShortcutResult) {
+            if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
                 return score * SHORTCUT_TARGET_SCORE_BOOST;
             }
 
diff --git a/core/java/android/view/Transaction.aidl b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
similarity index 74%
copy from core/java/android/view/Transaction.aidl
copy to core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
index 8d24b43..163e4f7 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/core/java/com/android/internal/app/IAppOpsAsyncNotedCallback.aidl
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+package com.android.internal.app;
 
-parcelable Transaction;
+import android.app.AsyncNotedAppOp;
+
+// Interface to observe async noted appops
+oneway interface IAppOpsAsyncNotedCallback {
+    void opNoted(in AsyncNotedAppOp op);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 72dbbf3..8f6c950 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -17,12 +17,13 @@
 package com.android.internal.app;
 
 import android.app.AppOpsManager;
-import android.app.AppOpsManager;
+import android.app.AsyncNotedAppOp;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.os.RemoteCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 
 interface IAppOpsService {
@@ -40,6 +41,10 @@
     IBinder getToken(IBinder clientToken);
     int permissionToOpCode(String permission);
     int checkAudioOperation(int code, int usage, int uid, String packageName);
+    void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+            String message);
+    boolean shouldCollectNotes(int opCode);
+    void setCameraAudioRestriction(int mode);
     // End of methods also called by native code.
     // Any new method exposed to native must be added after the last one, do not reorder
 
@@ -82,6 +87,10 @@
     void startWatchingNoted(in int[] ops, IAppOpsNotedCallback callback);
     void stopWatchingNoted(IAppOpsNotedCallback callback);
 
+    void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+    void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback);
+    List<AsyncNotedAppOp> extractAsyncOps(String packageName);
+
     int checkOperationRaw(int code, int uid, String packageName);
 
     void reloadNonHistoricalState();
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index 764c0cf..ea24d5f 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -54,4 +54,6 @@
     boolean isRecognitionActive(in ParcelUuid parcelUuid);
 
     int getModelState(in ParcelUuid soundModelId);
+
+    @nullable SoundTrigger.ModuleProperties getModuleProperties();
 }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 58ce03b..407a85f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -361,9 +361,6 @@
 
         initSuspendedColorMatrix();
 
-        if (isVoiceInteraction()) {
-            onSetupVoiceInteraction();
-        }
         final Set<String> categories = intent.getCategories();
         MetricsLogger.action(this, mAdapter.hasFilteredItem()
                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -442,24 +439,21 @@
         mSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix);
     }
 
-    /**
-     * Perform any initialization needed for voice interaction.
-     */
-    public void onSetupVoiceInteraction() {
-        // Do it right now. Subclasses may delay this and send it later.
-        sendVoiceChoicesIfNeeded();
-    }
-
     public void sendVoiceChoicesIfNeeded() {
         if (!isVoiceInteraction()) {
             // Clearly not needed.
             return;
         }
 
-
         final Option[] options = new Option[mAdapter.getCount()];
         for (int i = 0, N = options.length; i < N; i++) {
-            options[i] = optionForChooserTarget(mAdapter.getItem(i), i);
+            TargetInfo target = mAdapter.getItem(i);
+            if (target == null) {
+                // If this occurs, a new set of targets is being loaded. Let that complete,
+                // and have the next call to send voice choices proceed instead.
+                return;
+            }
+            options[i] = optionForChooserTarget(target, i);
         }
 
         mPickOptionRequest = new PickTargetOptionRequest(
@@ -1872,7 +1866,7 @@
                 }
             }
 
-
+            sendVoiceChoicesIfNeeded();
             postListReadyRunnable();
         }
 
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index a7f7fe1..7cbacad 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -27,5 +27,6 @@
     void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
     void providersChanged();
     void viewDataChanged(int appWidgetId, int viewId);
+    void appWidgetRemoved(int appWidgetId);
 }
 
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
new file mode 100644
index 0000000..1ce071b
--- /dev/null
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.util.StatsLog;
+
+/**
+ * A helper class to report changes to stats log.
+ *
+ * @hide
+ */
+public final class ChangeReporter {
+
+    /**
+     * Transforms StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE enum to a string.
+     *
+     * @param state to transform
+     * @return a string representing the state
+     */
+    private static String stateToString(int state) {
+        switch (state) {
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED:
+                return "LOGGED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED:
+                return "ENABLED";
+            case StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED:
+                return "DISABLED";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * Constructs and returns a string to be logged to logcat when a change is reported.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @return string to log
+     */
+    public static String createLogString(int uid, long changeId, int state) {
+        return String.format("Compat change id reported: %d; UID %d; state: %s", changeId, uid,
+                stateToString(state));
+    }
+
+    /**
+     * Report the change to stats log.
+     *
+     * @param uid      affected by the change
+     * @param changeId the reported change id
+     * @param state    of the reported change - enabled/disabled/only logged
+     * @param source   of the logging - app process or system server
+     */
+    public void reportChange(int uid, long changeId, int state, int source) {
+        //TODO(b/138374585): Implement rate limiting for stats log.
+        StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
+                state, source);
+    }
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 7c50337..5142d3c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -50,10 +50,19 @@
     // Flags related to controls
 
     /**
-     * (boolean) Wether to have split behavior when opening QS
+     * (boolean) Whether to have split behavior when opening QS
      */
     public static final String QS_SPLIT_ENABLED = "qs_split_enabled";
 
+    /**
+     * (int) Open settings panels for WiFi and BT tiles
+     * 0 - default behavior, link to settings
+     * 1 - open panel on long press, click remains the same
+     * 2 - open panel on click, long press remains the same
+     * 3 - use details on long press
+     */
+    public static final String QS_USE_SETTINGS_PANELS = "qs_use_settings_panels";
+
     // Flags related to Smart Suggestions - these are read in SmartReplyConstants.
 
     /** (boolean) Whether to enable smart suggestions in notifications. */
diff --git a/core/java/com/android/internal/os/AtomicFile.java b/core/java/com/android/internal/os/AtomicFile.java
deleted file mode 100644
index a72a2f5..0000000
--- a/core/java/com/android/internal/os/AtomicFile.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.os;
-
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
-import android.os.FileUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-/**
- * Helper class for performing atomic operations on a file, by creating a
- * backup file until a write has successfully completed.
- * <p>
- * Atomic file guarantees file integrity by ensuring that a file has
- * been completely written and sync'd to disk before removing its backup.
- * As long as the backup file exists, the original file is considered
- * to be invalid (left over from a previous attempt to write the file).
- * </p><p>
- * Atomic file does not confer any file locking semantics.
- * Do not use this class when the file may be accessed or modified concurrently
- * by multiple threads or processes.  The caller is responsible for ensuring
- * appropriate mutual exclusion invariants whenever it accesses the file.
- * </p>
- */
-public final class AtomicFile {
-    private final File mBaseName;
-    private final File mBackupName;
-    
-    @UnsupportedAppUsage
-    public AtomicFile(File baseName) {
-        mBaseName = baseName;
-        mBackupName = new File(baseName.getPath() + ".bak");
-    }
-    
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public File getBaseFile() {
-        return mBaseName;
-    }
-    
-    @UnsupportedAppUsage
-    public FileOutputStream startWrite() throws IOException {
-        // Rename the current file so it may be used as a backup during the next read
-        if (mBaseName.exists()) {
-            if (!mBackupName.exists()) {
-                if (!mBaseName.renameTo(mBackupName)) {
-                    Log.w("AtomicFile", "Couldn't rename file " + mBaseName
-                            + " to backup file " + mBackupName);
-                }
-            } else {
-                mBaseName.delete();
-            }
-        }
-        FileOutputStream str = null;
-        try {
-            str = new FileOutputStream(mBaseName);
-        } catch (FileNotFoundException e) {
-            File parent = mBaseName.getParentFile();
-            if (!parent.mkdir()) {
-                throw new IOException("Couldn't create directory " + mBaseName);
-            }
-            FileUtils.setPermissions(
-                parent.getPath(),
-                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                -1, -1);
-            try {
-                str = new FileOutputStream(mBaseName);
-            } catch (FileNotFoundException e2) {
-                throw new IOException("Couldn't create " + mBaseName);
-            }
-        }
-        return str;
-    }
-    
-    @UnsupportedAppUsage
-    public void finishWrite(FileOutputStream str) {
-        if (str != null) {
-            FileUtils.sync(str);
-            try {
-                str.close();
-                mBackupName.delete();
-            } catch (IOException e) {
-                Log.w("AtomicFile", "finishWrite: Got exception:", e);
-            }
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public void failWrite(FileOutputStream str) {
-        if (str != null) {
-            FileUtils.sync(str);
-            try {
-                str.close();
-                mBaseName.delete();
-                mBackupName.renameTo(mBaseName);
-            } catch (IOException e) {
-                Log.w("AtomicFile", "failWrite: Got exception:", e);
-            }
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public FileOutputStream openAppend() throws IOException {
-        try {
-            return new FileOutputStream(mBaseName, true);
-        } catch (FileNotFoundException e) {
-            throw new IOException("Couldn't append " + mBaseName);
-        }
-    }
-    
-    @UnsupportedAppUsage
-    public void truncate() throws IOException {
-        try {
-            FileOutputStream fos = new FileOutputStream(mBaseName);
-            FileUtils.sync(fos);
-            fos.close();
-        } catch (FileNotFoundException e) {
-            throw new IOException("Couldn't append " + mBaseName);
-        } catch (IOException e) {
-        }
-    }
-
-    public boolean exists() {
-        return mBaseName.exists() || mBackupName.exists();
-    }
-
-    public void delete() {
-        mBaseName.delete();
-        mBackupName.delete();
-    }
-
-    @UnsupportedAppUsage
-    public FileInputStream openRead() throws FileNotFoundException {
-        if (mBackupName.exists()) {
-            mBaseName.delete();
-            mBackupName.renameTo(mBaseName);
-        }
-        return new FileInputStream(mBaseName);
-    }
-    
-    @UnsupportedAppUsage
-    public byte[] readFully() throws IOException {
-        FileInputStream stream = openRead();
-        try {
-            int pos = 0;
-            int avail = stream.available();
-            byte[] data = new byte[avail];
-            while (true) {
-                int amt = stream.read(data, pos, data.length-pos);
-                //Log.i("foo", "Read " + amt + " bytes at " + pos
-                //        + " of avail " + data.length);
-                if (amt <= 0) {
-                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                    //        + " len=" + data.length);
-                    return data;
-                }
-                pos += amt;
-                avail = stream.available();
-                if (avail > data.length-pos) {
-                    byte[] newData = new byte[pos+avail];
-                    System.arraycopy(data, 0, newData, 0, pos);
-                    data = newData;
-                }
-            }
-        } finally {
-            stream.close();
-        }
-    }
-}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 392f074..bdb65f3 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -21,6 +21,7 @@
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 8503ab8..9bddd2a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -60,11 +60,13 @@
 import android.provider.Settings;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
+import android.telephony.ModemActivityInfo.TransmitPower;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.IntArray;
 import android.util.KeyValueListParser;
 import android.util.Log;
@@ -10975,7 +10977,7 @@
     }
 
     private ModemActivityInfo mLastModemActivityInfo =
-            new ModemActivityInfo(0, 0, 0, new int[0], 0, 0);
+            new ModemActivityInfo(0, 0, 0, new int[0], 0);
 
     private ModemActivityInfo getDeltaModemActivityInfo(ModemActivityInfo activityInfo) {
         if (activityInfo == null) {
@@ -10983,15 +10985,14 @@
         }
         int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS];
         for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) {
-            txTimeMs[i] = activityInfo.getTxTimeMillis()[i]
-                    - mLastModemActivityInfo.getTxTimeMillis()[i];
+            txTimeMs[i] = activityInfo.getTransmitPowerInfo().get(i).getTimeInMillis()
+                    - mLastModemActivityInfo.getTransmitPowerInfo().get(i).getTimeInMillis();
         }
         ModemActivityInfo deltaInfo = new ModemActivityInfo(activityInfo.getTimestamp(),
                 activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(),
                 activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(),
                 txTimeMs,
-                activityInfo.getRxTimeMillis() - mLastModemActivityInfo.getRxTimeMillis(),
-                activityInfo.getEnergyUsed() - mLastModemActivityInfo.getEnergyUsed());
+                activityInfo.getReceiveTimeMillis() - mLastModemActivityInfo.getReceiveTimeMillis());
         mLastModemActivityInfo = activityInfo;
         return deltaInfo;
     }
@@ -11034,10 +11035,11 @@
                         deltaInfo.getIdleTimeMillis());
                 mModemActivity.getSleepTimeCounter().addCountLocked(
                         deltaInfo.getSleepTimeMillis());
-                mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getRxTimeMillis());
+                mModemActivity.getRxTimeCounter().addCountLocked(deltaInfo.getReceiveTimeMillis());
                 for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                     mModemActivity.getTxTimeCounters()[lvl]
-                        .addCountLocked(deltaInfo.getTxTimeMillis()[lvl]);
+                        .addCountLocked(deltaInfo.getTransmitPowerInfo()
+                            .get(lvl).getTimeInMillis());
                 }
 
                 // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V.
@@ -11049,13 +11051,13 @@
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP)
                             + deltaInfo.getIdleTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)
-                            + deltaInfo.getRxTimeMillis() *
+                            + deltaInfo.getReceiveTimeMillis() *
                             mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
-                    int[] txTimeMs = deltaInfo.getTxTimeMillis();
-                    for (int i = 0; i < Math.min(txTimeMs.length,
+                    List<TransmitPower> txPowerInfo = deltaInfo.getTransmitPowerInfo();
+                    for (int i = 0; i < Math.min(txPowerInfo.size(),
                             SignalStrength.NUM_SIGNAL_STRENGTH_BINS); i++) {
-                        energyUsed += txTimeMs[i] * mPowerProfile.getAveragePower(
-                                PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
+                        energyUsed += txPowerInfo.get(i).getTimeInMillis() * mPowerProfile
+                            .getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, i);
                     }
 
                     // We store the power drain as mAms.
@@ -11144,15 +11146,16 @@
                             ControllerActivityCounterImpl activityCounter =
                                     u.getOrCreateModemControllerActivityLocked();
                             if (totalRxPackets > 0 && entry.rxPackets > 0) {
-                                final long rxMs = (entry.rxPackets * deltaInfo.getRxTimeMillis())
-                                        / totalRxPackets;
+                                final long rxMs = (entry.rxPackets
+                                    * deltaInfo.getReceiveTimeMillis()) / totalRxPackets;
                                 activityCounter.getRxTimeCounter().addCountLocked(rxMs);
                             }
 
                             if (totalTxPackets > 0 && entry.txPackets > 0) {
                                 for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) {
                                     long txMs =
-                                            entry.txPackets * deltaInfo.getTxTimeMillis()[lvl];
+                                            entry.txPackets * deltaInfo.getTransmitPowerInfo()
+                                                .get(lvl).getTimeInMillis();
                                     txMs /= totalTxPackets;
                                     activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs);
                                 }
@@ -11183,15 +11186,16 @@
         if (activityInfo == null) {
             return;
         }
-        int[] txTimeMs = activityInfo.getTxTimeMillis();
-        if (txTimeMs == null || txTimeMs.length != ModemActivityInfo.TX_POWER_LEVELS) {
+        List<TransmitPower> txPowerInfo = activityInfo.getTransmitPowerInfo();
+        if (txPowerInfo == null || txPowerInfo.size() != ModemActivityInfo.TX_POWER_LEVELS) {
             return;
         }
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         int levelMaxTimeSpent = 0;
-        for (int i = 1; i < txTimeMs.length; i++) {
-            if (txTimeMs[i] > txTimeMs[levelMaxTimeSpent]) {
+        for (int i = 1; i < txPowerInfo.size(); i++) {
+            if (txPowerInfo.get(i).getTimeInMillis() > txPowerInfo.get(levelMaxTimeSpent)
+                .getTimeInMillis()) {
                 levelMaxTimeSpent = i;
             }
         }
@@ -13321,7 +13325,8 @@
             return;
         }
 
-        if (mBatteryStatsHistory.getActiveFile() == null) {
+        final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
+        if (activeHistoryFile == null) {
             Slog.w(TAG,
                     "readLocked: no history file associated with this instance");
             return;
@@ -13332,14 +13337,16 @@
         Parcel stats = Parcel.obtain();
         try {
             final long start = SystemClock.uptimeMillis();
-            byte[] raw = mStatsFile.readFully();
-            stats.unmarshall(raw, 0, raw.length);
-            stats.setDataPosition(0);
-            readSummaryFromParcel(stats);
-            if (DEBUG) {
-                Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
-                        + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
-                        - start));
+            if (mStatsFile.exists()) {
+                byte[] raw = mStatsFile.readFully();
+                stats.unmarshall(raw, 0, raw.length);
+                stats.setDataPosition(0);
+                readSummaryFromParcel(stats);
+                if (DEBUG) {
+                    Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error reading battery statistics", e);
@@ -13351,17 +13358,19 @@
         Parcel history = Parcel.obtain();
         try {
             final long start = SystemClock.uptimeMillis();
-            byte[] raw = mBatteryStatsHistory.getActiveFile().readFully();
-            if (raw.length > 0) {
-                history.unmarshall(raw, 0, raw.length);
-                history.setDataPosition(0);
-                readHistoryBuffer(history, true);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "readLocked history file::"
-                        + mBatteryStatsHistory.getActiveFile().getBaseFile().getPath()
-                        + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
-                        - start));
+            if (activeHistoryFile.exists()) {
+                byte[] raw = activeHistoryFile.readFully();
+                if (raw.length > 0) {
+                    history.unmarshall(raw, 0, raw.length);
+                    history.setDataPosition(0);
+                    readHistoryBuffer(history, true);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "readLocked history file::"
+                            + activeHistoryFile.getBaseFile().getPath()
+                            + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+                            - start));
+                }
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error reading battery history", e);
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b5d4945..3be1a1a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -460,7 +460,7 @@
         ZygoteHooks.gcAndFinalize();
     }
 
-    private static boolean profileSystemServer() {
+    private static boolean shouldProfileSystemServer() {
         boolean defaultValue = SystemProperties.getBoolean("dalvik.vm.profilesystemserver",
                 /*default=*/ false);
         // Can't use DeviceConfig since it's not initialized at this point.
@@ -492,7 +492,7 @@
             }
             // Capturing profiles is only supported for debug or eng builds since selinux normally
             // prevents it.
-            if (profileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
+            if (shouldProfileSystemServer() && (Build.IS_USERDEBUG || Build.IS_ENG)) {
                 try {
                     Log.d(TAG, "Preparing system server profile");
                     prepareSystemServerProfile(systemServerClasspath);
@@ -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",
@@ -765,8 +765,7 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
-            if (profileSystemServer()) {
-
+            if (shouldProfileSystemServer()) {
                 parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 981d0bb..b02563a 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -804,7 +804,9 @@
         updateElevation();
         mAllowUpdateElevation = true;
 
-        if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
+        if (changed
+                && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
+                    || mDrawLegacyNavigationBarBackground)) {
             getViewRootImpl().requestInvalidateRootRenderNode();
         }
     }
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
index c8afdd4..2d3b450 100644
--- a/core/java/com/android/internal/util/AnnotationValidations.java
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -61,16 +61,52 @@
     }
 
     public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
-            String paramName1, int param1, String paramName2, int param2) {
+            String paramName1, long param1, String paramName2, long param2) {
         validate(annotation, ignored, value, paramName1, param1);
         validate(annotation, ignored, value, paramName2, param2);
     }
 
     public static void validate(Class<IntRange> annotation, IntRange ignored, int value,
-            String paramName, int param) {
+            String paramName, long param) {
         switch (paramName) {
-            case "from": if (value < param) invalid(annotation, value, paramName, param); break;
-            case "to": if (value > param) invalid(annotation, value, paramName, param); break;
+            case "from":
+                if (value < param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+            case "to":
+                if (value > param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Validate a long value with two parameters.
+     */
+    public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+            String paramName1, long param1, String paramName2, long param2) {
+        validate(annotation, ignored, value, paramName1, param1);
+        validate(annotation, ignored, value, paramName2, param2);
+    }
+
+    /**
+     * Validate a long value with one parameter.
+     */
+    public static void validate(Class<IntRange> annotation, IntRange ignored, long value,
+            String paramName, long param) {
+        switch (paramName) {
+            case "from":
+                if (value < param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
+            case "to":
+                if (value > param) {
+                    invalid(annotation, value, paramName, param);
+                }
+                break;
         }
     }
 
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 7fd94c6..cac691c 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,6 +1,7 @@
 package com.android.internal.util;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -13,6 +14,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import java.util.function.Consumer;
+
 public class ScreenshotHelper {
     private static final String TAG = "ScreenshotHelper";
 
@@ -34,17 +37,58 @@
     }
 
     /**
-     * Request a screenshot be taken.
+     * Request a screenshot be taken with a specific timeout.
      *
-     * @param screenshotType The type of screenshot, for example either
-     *                       {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
-     *                       or {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
-     * @param hasStatus {@code true} if the status bar is currently showing. {@code false} if not.
-     * @param hasNav {@code true} if the navigation bar is currently showing. {@code false} if not.
-     * @param handler A handler used in case the screenshot times out
+     * Added to support reducing unit test duration; the method variant without a timeout argument
+     * is recommended for general use.
+     *
+     * @param screenshotType     The type of screenshot, for example either
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
+     *                           or
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
+     *                           if
+     *                           not.
+     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
+     *                           false}
+     *                           if not.
+     * @param handler            A handler used in case the screenshot times out
+     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+     *                           screenshot was taken.
      */
     public void takeScreenshot(final int screenshotType, final boolean hasStatus,
-            final boolean hasNav, @NonNull Handler handler) {
+            final boolean hasNav, @NonNull Handler handler,
+            @Nullable Consumer<Boolean> completionConsumer) {
+        takeScreenshot(screenshotType, hasStatus, hasNav, SCREENSHOT_TIMEOUT_MS, handler,
+                completionConsumer);
+    }
+
+    /**
+     * Request a screenshot be taken.
+     *
+     * Added to support reducing unit test duration; the method variant without a timeout argument
+     * is recommended for general use.
+     *
+     * @param screenshotType     The type of screenshot, for example either
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN}
+     *                           or
+     *                           {@link android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION}
+     * @param hasStatus          {@code true} if the status bar is currently showing. {@code false}
+     *                           if
+     *                           not.
+     * @param hasNav             {@code true} if the navigation bar is currently showing. {@code
+     *                           false}
+     *                           if not.
+     * @param timeoutMs          If the screenshot hasn't been completed within this time period,
+     *                           the screenshot attempt will be cancelled and `completionConsumer`
+     *                           will be run.
+     * @param handler            A handler used in case the screenshot times out
+     * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+     *                           screenshot was taken.
+     */
+    public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+            final boolean hasNav, long timeoutMs, @NonNull Handler handler,
+            @Nullable Consumer<Boolean> completionConsumer) {
         synchronized (mScreenshotLock) {
             if (mScreenshotConnection != null) {
                 return;
@@ -54,7 +98,8 @@
             final Intent serviceIntent = new Intent();
 
             final Runnable mScreenshotTimeout = new Runnable() {
-                @Override public void run() {
+                @Override
+                public void run() {
                     synchronized (mScreenshotLock) {
                         if (mScreenshotConnection != null) {
                             mContext.unbindService(mScreenshotConnection);
@@ -62,6 +107,9 @@
                             notifyScreenshotError();
                         }
                     }
+                    if (completionConsumer != null) {
+                        completionConsumer.accept(false);
+                    }
                 }
             };
 
@@ -86,15 +134,22 @@
                                         handler.removeCallbacks(mScreenshotTimeout);
                                     }
                                 }
+                                if (completionConsumer != null) {
+                                    completionConsumer.accept(true);
+                                }
                             }
                         };
                         msg.replyTo = new Messenger(h);
-                        msg.arg1 = hasStatus ? 1: 0;
-                        msg.arg2 = hasNav ? 1: 0;
+                        msg.arg1 = hasStatus ? 1 : 0;
+                        msg.arg2 = hasNav ? 1 : 0;
+
                         try {
                             messenger.send(msg);
                         } catch (RemoteException e) {
                             Log.e(TAG, "Couldn't take screenshot: " + e);
+                            if (completionConsumer != null) {
+                                completionConsumer.accept(false);
+                            }
                         }
                     }
                 }
@@ -115,7 +170,7 @@
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                     UserHandle.CURRENT)) {
                 mScreenshotConnection = conn;
-                handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS);
+                handler.postDelayed(mScreenshotTimeout, timeoutMs);
             }
         }
     }
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index 3cb6919..b54e5fb 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -229,6 +229,7 @@
      *                    default (undefined) change type or one or more of:
      *         <ul>
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+     *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_SUBTREE}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_TEXT}
      *         <li>{@link AccessibilityEvent#CONTENT_CHANGE_TYPE_UNDEFINED}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index b36c3fa..b626fc6 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -42,7 +42,7 @@
     long getLong(in String key, in long defaultValue, in int userId);
     @UnsupportedAppUsage
     String getString(in String key, in String defaultValue, in int userId);
-    void setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
+    boolean setLockCredential(in byte[] credential, int type, in byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange);
     void resetKeyStore(int userId);
     VerifyCredentialResponse checkCredential(in byte[] credential, int type, int userId,
             in ICheckCredentialProgressCallback progressCallback);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 32867a8..070121c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -680,6 +680,15 @@
 
     /**
      * Clear any lock pattern or password.
+
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
+     * @param savedCredential The previously saved credential
+     * @param userHandle the user whose pattern is to be saved.
+     * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean clearLock(byte[] savedCredential, int userHandle) {
         return clearLock(savedCredential, userHandle, false);
@@ -687,19 +696,27 @@
 
     /**
      * Clear any lock pattern or password, with the option to ignore incorrect existing credential.
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
+     * @param savedCredential The previously saved credential
+     * @param userHandle the user whose pattern is to be saved.
+     * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean clearLock(byte[] savedCredential, int userHandle, boolean allowUntrustedChange) {
         final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
         setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userHandle);
 
-        try{
-            getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE,
-                    savedCredential, PASSWORD_QUALITY_UNSPECIFIED, userHandle,
-                    allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to clear lock", e);
+        try {
+            if (!getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
+                    PASSWORD_QUALITY_UNSPECIFIED, userHandle, allowUntrustedChange)) {
+                return false;
+            }
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            return false;
+            throw new RuntimeException("Failed to clear lock", e);
         }
 
         if (userHandle == UserHandle.USER_SYSTEM) {
@@ -747,11 +764,15 @@
 
     /**
      * Save a lock pattern.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+     * is incorrect, or if the lockscreen verification is still being throttled.
+     *
      * @param pattern The new pattern to save.
      * @param savedPattern The previously saved pattern, converted to byte[] format
      * @param userId the user whose pattern is to be saved.
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
             int userId) {
@@ -760,13 +781,17 @@
 
     /**
      * Save a lock pattern.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+     * is incorrect, or if the lockscreen verification is still being throttled.
+     *
      * @param pattern The new pattern to save.
      * @param savedPattern The previously saved pattern, converted to byte[] format
      * @param userId the user whose pattern is to be saved.
      * @param allowUntrustedChange whether we want to allow saving a new password if the existing
      * password being provided is incorrect.
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
             int userId, boolean allowUntrustedChange) {
@@ -783,12 +808,13 @@
         final int currentQuality = getKeyguardStoredPasswordQuality(userId);
         setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId);
         try {
-            getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN, savedPattern,
-                    PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Couldn't save lock pattern", e);
+            if (!getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN,
+                    savedPattern, PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange)) {
+                return false;
+            }
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userId);
-            return false;
+            throw new RuntimeException("Couldn't save lock pattern", e);
         }
         // Update the device encryption password.
         if (userId == UserHandle.USER_SYSTEM
@@ -906,14 +932,18 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
      * android.content.ComponentName)}
      * @param userHandle The userId of the user to change the password for
-     *
      * @return whether this was successful or not.
-     *
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      * @deprecated Pass password as a byte array
      */
     @Deprecated
@@ -928,13 +958,18 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
      * android.content.ComponentName)}
      * @param userHandle The userId of the user to change the password for
-     *
      * @return whether this was successful or not.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
             int userHandle) {
@@ -946,6 +981,11 @@
      * Save a lock password.  Does not ensure that the password is as good
      * as the requested mode, but will adjust the mode to be as good as the
      * password.
+     *
+     * <p> This method will fail (returning {@code false}) if the previously
+     * saved password provided is incorrect, or if the lockscreen verification
+     * is still being throttled.
+     *
      * @param password The password to save
      * @param savedPassword The previously saved lock password, or null if none
      * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
@@ -953,9 +993,9 @@
      * @param userHandle The userId of the user to change the password for
      * @param allowUntrustedChange whether we want to allow saving a new password if the existing
      * password being provided is incorrect.
-     *
      * @return whether this method saved the new password successfully or not. This flow will fail
      * and return false if the given credential is wrong and allowUntrustedChange is false.
+     * @throws RuntimeException if password change encountered an unrecoverable error.
      */
     public boolean saveLockPassword(byte[] password, byte[] savedPassword,
             int requestedQuality, int userHandle, boolean allowUntrustedChange) {
@@ -981,10 +1021,9 @@
         try {
             getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
                     requestedQuality, userHandle, allowUntrustedChange);
-        } catch (Exception e) {
-            Log.e(TAG, "Unable to save lock password", e);
+        } catch (RemoteException | RuntimeException e) {
             setKeyguardStoredPasswordQuality(currentQuality, userHandle);
-            return false;
+            throw new RuntimeException("Unable to save lock password", e);
         }
 
         updateEncryptionPasswordIfNeeded(password, passwordQuality, userHandle);
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 364278d..7cd3e95 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -53,6 +53,8 @@
 
 /**
  * Loads global system configuration info.
+ * Note: Initializing this class hits the disk and is slow.  This class should generally only be
+ * accessed by the system_server process.
  */
 public class SystemConfig {
     static final String TAG = "SystemConfig";
@@ -208,6 +210,11 @@
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
 
     public static SystemConfig getInstance() {
+        if (!isSystemProcess()) {
+            Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
+                    + "system_server.");
+        }
+
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
                 sInstance = new SystemConfig();
@@ -1161,4 +1168,8 @@
             mSplitPermissions.add(new SplitPermissionInfo(splitPerm, newPermissions, targetSdk));
         }
     }
+
+    private static boolean isSystemProcess() {
+        return Process.myUid() == Process.SYSTEM_UID;
+    }
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 4c54539..5a0f16e 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -42,10 +42,6 @@
     ],
 
     include_dirs: [
-        // we need to access the private Bionic header
-        // <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
-        "bionic/libc/private",
-
         "external/skia/include/private",
         "frameworks/base/media/jni",
         "system/media/camera/include",
@@ -277,6 +273,7 @@
                 "libnativewindow",
             ],
             generated_sources: ["android_util_StatsLogInternal.cpp"],
+            header_libs: ["bionic_libc_platform_headers"],
         },
         host: {
             cflags: [
@@ -308,6 +305,7 @@
                 "android_os_MessageQueue.cpp",
                 "android_os_Trace.cpp",
                 "android_util_AssetManager.cpp",
+                "android_util_FileObserver.cpp",
                 "android_util_StringBlock.cpp",
                 "android_util_XmlBlock.cpp",
             ],
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 549fd4d..6c0680f 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -70,6 +70,7 @@
 extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
 extern int register_android_graphics_text_LineBreaker(JNIEnv* env);
 extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
+extern int register_android_os_FileObserver(JNIEnv* env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
@@ -128,6 +129,7 @@
     {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
     {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)},
 #ifdef __linux__
+    {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
     {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
 #endif
     {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
diff --git a/core/jni/android_app_ActivityThread.cpp b/core/jni/android_app_ActivityThread.cpp
index 5f83038..ca8b8de 100644
--- a/core/jni/android_app_ActivityThread.cpp
+++ b/core/jni/android_app_ActivityThread.cpp
@@ -23,7 +23,7 @@
 #include "core_jni_helpers.h"
 #include <unistd.h>
 
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 namespace android {
 
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 076e99d..2ca4500 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -23,7 +23,7 @@
 #include "core_jni_helpers.h"
 
 #include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 #include <utils/Log.h>
 #include <utils/String8.h>
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index b499981..bc69735 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -1023,6 +1023,41 @@
     }
 }
 
+static void android_hardware_Camera_setAudioRestriction(
+        JNIEnv *env, jobject thiz, jint mode)
+{
+    ALOGV("setAudioRestriction");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) {
+        jniThrowRuntimeException(env, "camera has been disconnected");
+        return;
+    }
+
+    int32_t ret = camera->setAudioRestriction(mode);
+    if (ret < 0) {
+        jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+        return;
+    }
+}
+
+static int32_t android_hardware_Camera_getAudioRestriction(
+        JNIEnv *env, jobject thiz)
+{
+    ALOGV("getAudioRestriction");
+    sp<Camera> camera = get_native_camera(env, thiz, NULL);
+    if (camera == 0) {
+        jniThrowRuntimeException(env, "camera has been disconnected");
+        return -1;
+    }
+
+    int32_t ret = camera->getGlobalAudioRestriction();
+    if (ret < 0) {
+        jniThrowRuntimeException(env, "Illegal argument or low-level eror");
+        return -1;
+    }
+    return ret;
+}
+
 //-------------------------------------------------
 
 static const JNINativeMethod camMethods[] = {
@@ -1107,6 +1142,12 @@
   { "enableFocusMoveCallback",
     "(I)V",
     (void *)android_hardware_Camera_enableFocusMoveCallback},
+  { "setAudioRestriction",
+    "(I)V",
+    (void *)android_hardware_Camera_setAudioRestriction},
+  { "getAudioRestriction",
+    "()I",
+    (void *)android_hardware_Camera_getAudioRestriction},
 };
 
 struct field {
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 03057dc..0002f8b 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -606,12 +606,12 @@
         goto exit;
     }
     memory = memoryDealer->allocate(offset + size);
-    if (memory == 0 || memory->pointer() == NULL) {
+    if (memory == 0 || memory->unsecurePointer() == NULL) {
         status = SOUNDTRIGGER_STATUS_ERROR;
         goto exit;
     }
 
-    nSoundModel = (struct sound_trigger_sound_model *)memory->pointer();
+    nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
 
     nSoundModel->type = type;
     nSoundModel->uuid = nUuid;
@@ -737,18 +737,18 @@
         return SOUNDTRIGGER_STATUS_ERROR;
     }
     sp<IMemory> memory = memoryDealer->allocate(totalSize);
-    if (memory == 0 || memory->pointer() == NULL) {
+    if (memory == 0 || memory->unsecurePointer() == NULL) {
         return SOUNDTRIGGER_STATUS_ERROR;
     }
     if (dataSize != 0) {
-        memcpy((char *)memory->pointer() + sizeof(struct sound_trigger_recognition_config),
+        memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
                 nData,
                 dataSize);
         env->ReleaseByteArrayElements(jData, nData, 0);
     }
     env->DeleteLocalRef(jData);
     struct sound_trigger_recognition_config *config =
-                                    (struct sound_trigger_recognition_config *)memory->pointer();
+                                    (struct sound_trigger_recognition_config *)memory->unsecurePointer();
     config->data_size = dataSize;
     config->data_offset = sizeof(struct sound_trigger_recognition_config);
     config->capture_requested = env->GetBooleanField(jConfig,
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index daa6347..c5049ec 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -649,7 +649,7 @@
         if ((size_t)sizeInBytes > track->sharedBuffer()->size()) {
             sizeInBytes = track->sharedBuffer()->size();
         }
-        memcpy(track->sharedBuffer()->pointer(), data + offsetInSamples, sizeInBytes);
+        memcpy(track->sharedBuffer()->unsecurePointer(), data + offsetInSamples, sizeInBytes);
         written = sizeInBytes;
     }
     if (written >= 0) {
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 13e1dfa..9c52a64 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -34,7 +34,7 @@
 #include <vector>
 
 #include <android-base/logging.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 #include <debuggerd/client.h>
 #include <log/log.h>
 #include <utils/misc.h>
@@ -564,11 +564,13 @@
 
     // Read system memory info including ZRAM. The values are stored in the vector
     // in the same order as MEMINFO_* enum
-    std::vector<uint64_t> mem(MEMINFO_COUNT);
-    std::vector<std::string> tags(::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags);
+    std::vector<std::string_view> tags(
+        ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.begin(),
+        ::android::meminfo::SysMemInfo::kDefaultSysMemInfoTags.end());
     tags.insert(tags.begin() + MEMINFO_ZRAM_TOTAL, "Zram:");
+    std::vector<uint64_t> mem(tags.size());
     ::android::meminfo::SysMemInfo smi;
-    if (!smi.ReadMemInfo(tags, &mem)) {
+    if (!smi.ReadMemInfo(tags.size(), tags.data(), mem.data())) {
         jniThrowRuntimeException(env, "SysMemInfo read failed");
         return;
     }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index cbae2da..b6427c9a 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -152,7 +152,7 @@
         uint32_t flags,
         TransactCallback callback) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    bool isOneway = (flags & TF_ONE_WAY) != 0;
+    bool isOneway = (flags & IBinder::FLAG_ONEWAY) != 0;
     ScopedLocalRef<jobject> replyObj(env, nullptr);
     sp<JHwParcel> replyContext = nullptr;
 
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index ab52314..cfdf26a 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -108,7 +108,7 @@
     if (!ConvertKeyAndForward(env, keyJ, true, handler)) {
         // Must have been a failure in SetProperty.
         jniThrowException(env, "java/lang/RuntimeException",
-                          "failed to set system property");
+                          "failed to set system property (check logcat for reason)");
     }
 }
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 2d7069c..8aa6f86 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,7 +28,12 @@
 #include <meminfo/sysmeminfo.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/sched_policy.h>
+#include <android-base/unique_fd.h>
 
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -58,8 +63,18 @@
 
 static const bool kDebugPolicy = false;
 static const bool kDebugProc = false;
-// When reading `proc` files, how many bytes to read at a time
-static const int kReadSize = 4096;
+
+// Stack reservation for reading small proc files.  Most callers of
+// readProcFile() are reading files under this threshold, e.g.,
+// /proc/pid/stat.  /proc/pid/time_in_state ends up being about 520
+// bytes, so use 1024 for the stack to provide a bit of slack.
+static const ssize_t kProcReadStackBufferSize = 1024;
+
+// The other files we read from proc tend to be a bit larger (e.g.,
+// /proc/stat is about 3kB), so once we exhaust the stack buffer,
+// retry with a relatively large heap-allocated buffer.  We double
+// this size and retry until the whole file fits.
+static const ssize_t kProcReadMinHeapBufferSize = 4096;
 
 #if GUARD_THREAD_PRIORITY
 Mutex gKeyCreateMutex;
@@ -616,14 +631,16 @@
 
 static jlong android_os_Process_getFreeMemory(JNIEnv* env, jobject clazz)
 {
-    static const std::vector<std::string> memFreeTags = {
+    std::array<std::string_view, 2> memFreeTags = {
         ::android::meminfo::SysMemInfo::kMemFree,
         ::android::meminfo::SysMemInfo::kMemCached,
     };
     std::vector<uint64_t> mem(memFreeTags.size());
     ::android::meminfo::SysMemInfo smi;
 
-    if (!smi.ReadMemInfo(memFreeTags, &mem)) {
+    if (!smi.ReadMemInfo(memFreeTags.size(),
+                         memFreeTags.data(),
+                         mem.data())) {
         jniThrowRuntimeException(env, "SysMemInfo read failed to get Free Memory");
         return -1L;
     }
@@ -644,6 +661,12 @@
     return si.totalram;
 }
 
+/*
+ * The outFields array is initialized to -1 to allow the caller to identify
+ * when the status file (and therefore the process) they specified is invalid.
+ * This array should not be overwritten or cleared before we know that the
+ * status file can be read.
+ */
 void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileStr,
                                       jobjectArray reqFields, jlongArray outFields)
 {
@@ -692,14 +715,14 @@
         return;
     }
 
-    //ALOGI("Clearing %" PRId32 " sizes", count);
-    for (i=0; i<count; i++) {
-        sizesArray[i] = 0;
-    }
-
     int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
 
     if (fd >= 0) {
+        //ALOGI("Clearing %" PRId32 " sizes", count);
+        for (i=0; i<count; i++) {
+            sizesArray[i] = 0;
+        }
+
         const size_t BUFFER_SIZE = 4096;
         char* buffer = (char*)malloc(BUFFER_SIZE);
         int len = read(fd, buffer, BUFFER_SIZE-1);
@@ -1004,9 +1027,9 @@
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return JNI_FALSE;
     }
-    int fd = open(file8, O_RDONLY | O_CLOEXEC);
 
-    if (fd < 0) {
+    ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+    if (!fd.ok()) {
         if (kDebugProc) {
             ALOGW("Unable to open process file: %s\n", file8);
         }
@@ -1015,35 +1038,52 @@
     }
     env->ReleaseStringUTFChars(file, file8);
 
-    std::vector<char> fileBuffer(kReadSize);
-    int numBytesRead = 0;
-    while (true) {
-        // Resize buffer to make space for contents. This might be more than we need, but once we've
-        // read we resize back down
-        fileBuffer.resize(numBytesRead + kReadSize, 0);
-        // Read in contents
-        int len = TEMP_FAILURE_RETRY(read(fd, fileBuffer.data() + numBytesRead, kReadSize));
-        numBytesRead += len;
-        if (len < 0) {
-            // If `len` is negative, an error occurred on read
+    // Most proc files we read are small, so we only go through the
+    // loop once and use the stack buffer.  We allocate a buffer big
+    // enough for the whole file.
+
+    char readBufferStack[kProcReadStackBufferSize];
+    std::unique_ptr<char[]> readBufferHeap;
+    char* readBuffer = &readBufferStack[0];
+    ssize_t readBufferSize = kProcReadStackBufferSize;
+    ssize_t numberBytesRead;
+    for (;;) {
+        // By using pread, we can avoid an lseek to rewind the FD
+        // before retry, saving a system call.
+        numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
+        if (numberBytesRead < 0 && errno == EINTR) {
+            continue;
+        }
+        if (numberBytesRead < 0) {
             if (kDebugProc) {
-                ALOGW("Unable to open process file: %s fd=%d\n", file8, fd);
+                ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
             }
-            close(fd);
             return JNI_FALSE;
-        } else if (len == 0) {
-            // If nothing read, we're done
+        }
+        if (numberBytesRead < readBufferSize) {
             break;
         }
+        if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+            if (kDebugProc) {
+                ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+            }
+            return JNI_FALSE;
+        }
+        readBufferSize = std::max(readBufferSize * 2,
+                                  kProcReadMinHeapBufferSize);
+        readBufferHeap.reset();  // Free address space before getting more.
+        readBufferHeap = std::make_unique<char[]>(readBufferSize);
+        if (!readBufferHeap) {
+            jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+            return JNI_FALSE;
+        }
+        readBuffer = readBufferHeap.get();
     }
-    // Resize back down to the amount we read
-    fileBuffer.resize(numBytesRead);
-    // Terminate buffer with null byte
-    fileBuffer.push_back('\0');
-    close(fd);
 
-    return android_os_Process_parseProcLineArray(env, clazz, fileBuffer.data(), 0, numBytesRead,
-            format, outStrings, outLongs, outFloats);
+    // parseProcLineArray below modifies the buffer while parsing!
+    return android_os_Process_parseProcLineArray(
+        env, clazz, readBuffer, 0, numberBytesRead,
+        format, outStrings, outLongs, outFloats);
 }
 
 void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 8553a2c..af34e7b7 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "InputChannel-JNI"
 
+#include "android-base/stringprintf.h"
 #include <nativehelper/JNIHelp.h>
 #include "nativehelper/scoped_utf_chars.h"
 #include <android_runtime/AndroidRuntime.h>
@@ -60,7 +61,7 @@
 // ----------------------------------------------------------------------------
 
 NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
-    mInputChannel(inputChannel), mDisposeCallback(NULL) {
+    mInputChannel(inputChannel), mDisposeCallback(nullptr) {
 }
 
 NativeInputChannel::~NativeInputChannel() {
@@ -74,8 +75,8 @@
 void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
     if (mDisposeCallback) {
         mDisposeCallback(env, obj, mInputChannel, mDisposeData);
-        mDisposeCallback = NULL;
-        mDisposeData = NULL;
+        mDisposeCallback = nullptr;
+        mDisposeData = nullptr;
     }
 }
 
@@ -96,14 +97,14 @@
 sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
-    return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
+    return nativeInputChannel != nullptr ? nativeInputChannel->getInputChannel() : nullptr;
 }
 
 void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
         InputChannelObjDisposeCallback callback, void* data) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
-    if (nativeInputChannel == NULL) {
+    if (nativeInputChannel == nullptr) {
         ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
     } else {
         nativeInputChannel->setDisposeCallback(callback, data);
@@ -131,27 +132,27 @@
     status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
 
     if (result) {
-        String8 message;
-        message.appendFormat("Could not open input channel pair.  status=%d", result);
-        jniThrowRuntimeException(env, message.string());
-        return NULL;
+        std::string message = android::base::StringPrintf(
+                "Could not open input channel pair : %s", strerror(-result));
+        jniThrowRuntimeException(env, message.c_str());
+        return nullptr;
     }
 
-    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
+    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
             std::make_unique<NativeInputChannel>(serverChannel));
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
             std::make_unique<NativeInputChannel>(clientChannel));
     if (env->ExceptionCheck()) {
-        return NULL;
+        return nullptr;
     }
 
     env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
@@ -170,7 +171,7 @@
 
         nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
 
-        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+        android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
         delete nativeInputChannel;
     }
 }
@@ -179,14 +180,14 @@
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     if (nativeInputChannel) {
-        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+        android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
         delete nativeInputChannel;
     }
 }
 
 static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
         jobject otherObj) {
-    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
+    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != nullptr) {
         jniThrowException(env, "java/lang/IllegalStateException",
                 "Other object already has a native input channel.");
         return;
@@ -195,12 +196,12 @@
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
-    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+    android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
 }
 
 static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
         jobject parcelObj) {
-    if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
+    if (android_view_InputChannel_getNativeInputChannel(env, obj) != nullptr) {
         jniThrowException(env, "java/lang/IllegalStateException",
                 "This object already has a native input channel.");
         return;
@@ -222,25 +223,26 @@
 static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
         jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
-    if (parcel) {
-        NativeInputChannel* nativeInputChannel =
-                android_view_InputChannel_getNativeInputChannel(env, obj);
-        if (nativeInputChannel) {
-            sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
-
-            parcel->writeInt32(1);
-            inputChannel->write(*parcel);
-        } else {
-            parcel->writeInt32(0);
-        }
+    if (parcel == nullptr) {
+        ALOGE("Could not obtain parcel for Java object");
+        return;
     }
+
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, obj);
+    if (!nativeInputChannel) {
+        parcel->writeInt32(0); // not initialized
+        return;
+    }
+    parcel->writeInt32(1); // initialized
+    nativeInputChannel->getInputChannel()->write(*parcel);
 }
 
 static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
     if (! nativeInputChannel) {
-        return NULL;
+        return nullptr;
     }
 
     jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().c_str());
@@ -250,10 +252,24 @@
 static void android_view_InputChannel_nativeDup(JNIEnv* env, jobject obj, jobject otherObj) {
     NativeInputChannel* nativeInputChannel =
             android_view_InputChannel_getNativeInputChannel(env, obj);
-    if (nativeInputChannel) {
-        android_view_InputChannel_setNativeInputChannel(env, otherObj,
-                new NativeInputChannel(nativeInputChannel->getInputChannel()->dup()));
+    if (nativeInputChannel == nullptr) {
+        jniThrowRuntimeException(env, "InputChannel has no valid NativeInputChannel");
+        return;
     }
+
+    sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
+    if (inputChannel == nullptr) {
+        jniThrowRuntimeException(env, "NativeInputChannel has no corresponding InputChannel");
+        return;
+    }
+    sp<InputChannel> dupInputChannel = inputChannel->dup();
+    if (dupInputChannel == nullptr) {
+        std::string message = android::base::StringPrintf(
+                "Could not duplicate input channel %s", inputChannel->getName().c_str());
+        jniThrowRuntimeException(env, message.c_str());
+    }
+    android_view_InputChannel_setNativeInputChannel(env, otherObj,
+            new NativeInputChannel(dupInputChannel));
 }
 
 static jobject android_view_InputChannel_nativeGetToken(JNIEnv* env, jobject obj) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 7975c86..c380fd5 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -83,7 +83,7 @@
         return mInputConsumer.getChannel()->getName();
     }
 
-    virtual int handleEvent(int receiveFd, int events, void* data);
+    virtual int handleEvent(int receiveFd, int events, void* data) override;
 };
 
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 67f52f4..bf0f10e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -397,15 +397,6 @@
     transaction->setGeometry(ctrl, source, dst, orientation);
 }
 
-static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
-jlong transactionObj,
-        jlong nativeObject) {
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
-    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    transaction->setGeometryAppliesWithResize(ctrl);
-}
-
 static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jint w, jint h) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1295,8 +1286,6 @@
             (void*)nativeSetRelativeLayer },
     {"nativeSetPosition", "(JJFF)V",
             (void*)nativeSetPosition },
-    {"nativeSetGeometryAppliesWithResize", "(JJ)V",
-            (void*)nativeSetGeometryAppliesWithResize },
     {"nativeSetSize", "(JJII)V",
             (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d5b875b..d42a48a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -73,7 +73,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 #include <cutils/ashmem.h>
 #include <cutils/fs.h>
 #include <cutils/multiuser.h>
diff --git a/core/proto/Android.bp b/core/proto/Android.bp
index 3b891d6..e199dab 100644
--- a/core/proto/Android.bp
+++ b/core/proto/Android.bp
@@ -28,3 +28,13 @@
         "android/bluetooth/smp/enums.proto",
     ],
 }
+
+java_library_host {
+    name: "windowmanager-log-proto",
+    srcs: [
+        "android/server/windowmanagerlog.proto"
+    ],
+    proto: {
+        type: "full",
+    },
+}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 3323095..b8c5270 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2402,6 +2402,10 @@
     // Note: Gear icon is shown next to gesture navigation preference and opens sensitivity dialog
     SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG = 1748;
 
+    // OPEN: Settings > System > Aware > Aware Display
+    // CATEGORY: SETTINGS
+    // OS: Q
+    SETTINGS_AWARE_DISPLAY = 1750;
     // ---- End Q Constants, all Q constants go above this line ----
     // OPEN: Settings > Network & Internet > Wi-Fi > Click new network
     // CATEGORY: SETTINGS
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 57ced09..7fa0ff6 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -32,7 +32,7 @@
     optional float font_scale = 1;
     optional uint32 mcc = 2;
     optional uint32 mnc = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
-    repeated LocaleProto locales = 4;
+    repeated LocaleProto locales = 4 [deprecated = true];
     optional uint32 screen_layout = 5;
     optional uint32 color_mode = 6;
     optional uint32 touchscreen = 7;
@@ -48,6 +48,7 @@
     optional uint32 smallest_screen_width_dp = 17;
     optional uint32 density_dpi = 18;
     optional .android.app.WindowConfigurationProto window_configuration = 19;
+    optional string locale_list = 20;
 }
 
 /**
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
index bae6ec1..a8f2a13 100644
--- a/core/proto/android/content/locale.proto
+++ b/core/proto/android/content/locale.proto
@@ -22,6 +22,7 @@
 package android.content;
 
 message LocaleProto {
+    option deprecated = true;
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
     optional string language = 1;
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index f2ca0a4..a568c13 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -688,6 +688,7 @@
         // Configuration options for smart replies and smart actions in notifications. This is
         // encoded as a key=value list separated by commas.
         optional SettingProto smart_suggestions_in_notifications_flags = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto bubbles = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Notification notification = 82;
 
diff --git a/core/proto/android/server/usagestatsservice.proto b/core/proto/android/server/usagestatsservice.proto
index f26eefa..75f265e 100644
--- a/core/proto/android/server/usagestatsservice.proto
+++ b/core/proto/android/server/usagestatsservice.proto
@@ -114,4 +114,6 @@
   repeated UsageStats packages = 20;
   repeated Configuration configurations = 21;
   repeated Event event_log = 22;
+
+  repeated Event pending_events = 23; // TODO: move to usagestatsservice_v2.proto
 }
diff --git a/core/proto/android/server/windowmanagerlog.proto b/core/proto/android/server/windowmanagerlog.proto
new file mode 100644
index 0000000..5bee1bd
--- /dev/null
+++ b/core/proto/android/server/windowmanagerlog.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.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/proto/android/stats/docsui/docsui_enums.proto b/core/proto/android/stats/docsui/docsui_enums.proto
index 655b5e3..f648912 100644
--- a/core/proto/android/stats/docsui/docsui_enums.proto
+++ b/core/proto/android/stats/docsui/docsui_enums.proto
@@ -184,6 +184,8 @@
     TYPE_CHIP_DOCS = 4;
     TYPE_SEARCH_HISTORY = 5;
     TYPE_SEARCH_STRING = 6;
+    TYPE_CHIP_LARGE_FILES = 7;
+    TYPE_CHIP_FROM_THIS_WEEK = 8;
 }
 
 enum SearchMode {
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 49c0a29..984be2a 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -33,4 +33,5 @@
     // The human-readable name of the display.
     // Eg: "Built-in Screen"
     optional string name = 5;
+    optional int32 flags = 6;
 }
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 075ebcf..93a9fe2 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -68,4 +68,6 @@
     optional uint32 private_flags = 26;
     optional uint32 system_ui_visibility_flags = 27;
     optional uint32 subtree_system_ui_visibility_flags = 28;
+    optional uint32 appearance = 29;
+    optional uint32 behavior = 30;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9738759..e23c75e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2008,6 +2008,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.
diff --git a/core/res/res/drawable/media_seamless_background.xml b/core/res/res/drawable/media_seamless_background.xml
new file mode 100644
index 0000000..aec89e0
--- /dev/null
+++ b/core/res/res/drawable/media_seamless_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="#1f000000">
+    <item android:id="@android:id/background">
+        <shape android:shape="rectangle">
+            <stroke android:width="1dp" android:color="#1f000000"/>
+            <corners android:radius="20dp"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/core/res/res/layout/notification_material_media_transfer_action.xml b/core/res/res/layout/notification_material_media_transfer_action.xml
new file mode 100644
index 0000000..98d8f1e
--- /dev/null
+++ b/core/res/res/layout/notification_material_media_transfer_action.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:visibility="gone"
+        android:padding="4dp"
+        android:layout_marginStart="10dp"
+        android:gravity="center"
+        android:background="@drawable/media_seamless_background">
+    <ImageView
+        android:layout_width="?attr/notificationHeaderIconSize"
+        android:layout_height="?attr/notificationHeaderIconSize"
+        android:src="@drawable/ic_media_seamless"
+        android:id="@+id/media_seamless_image" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="?attr/notificationHeaderTextAppearance"
+        android:text="@string/ext_media_seamless_action"
+        android:id="@+id/media_seamless_text"
+        android:paddingEnd="2dp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 0f53549..f5fa1b6a 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -175,5 +175,9 @@
             android:contentDescription="@string/notification_appops_overlay_active"
             />
     </LinearLayout>
+    <include
+        layout="@layout/notification_material_media_transfer_action"
+        android:id="@+id/media_seamless"
+    />
 </NotificationHeaderView>
 
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 3267f72..6c47c2c 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -80,10 +80,6 @@
                 layout="@layout/notification_material_media_action"
                 android:id="@+id/action4"
             />
-            <include
-                layout="@layout/notification_material_media_action"
-                android:id="@+id/media_seamless"
-            />
         </LinearLayout>
         <ViewStub android:id="@+id/notification_media_seekbar_container"
             android:layout_width="match_parent"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index c9500a6..a44b137a 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerke te sien"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koppel"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netwerke"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Laat voorgestelde Wi‑Fi-netwerke toe?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Netwerke wat deur <xliff:g id="NAME">%s</xliff:g> voorgestel is. Toestel sal dalk outomaties koppel."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Laat toe"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nee, dankie"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sal outomaties aanskakel"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer jy naby \'n gestoorde hoëgehaltenetwerk is"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Moenie weer aanskakel nie"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ongekategoriseer"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Jy stel die belangrikheid van hierdie kennisgewings."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dit is belangrik as gevolg van die mense wat betrokke is."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om \'n nuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> te skep?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Laat <xliff:g id="APP">%1$s</xliff:g> toe om \'n nuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> te skep (\'n gebruiker met hierdie rekening bestaan reeds)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Voeg \'n taal by"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Streekvoorkeur"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Voer taalnaam in"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 4761db2..db758e3 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ሁሉንም አውታረ መረቦችን ለማየት መታ ያድርጉ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"አገናኝ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ሁሉም አውታረ መረቦች"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"የተጠቆሙ የWi‑Fi አውታረ መረቦች ይፈቀዱ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"በ<xliff:g id="NAME">%s</xliff:g> የተጠቆሙ አውታረ መረቦች። መሣሪያ በራስ-ሰር ሊገናኝ ይችላል።"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ፍቀድ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"አይ፣ አመሰግናለሁ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi በራስ-ሰር ይበራል"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ከፍተኛ ጥራት ያለው የተቀመጠ አውታረ መረብ አቅራቢያ ሲሆኑ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"መልሰህ አታብራ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ያልተመደቡ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"የእነዚህን ማሳወቂያዎች አስፈላጊነት አዘጋጅተዋል።"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ይሄ በሚሳተፉ ሰዎች ምክንያት አስፈላጊ ነው።"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> በ<xliff:g id="ACCOUNT">%2$s</xliff:g> አዲስ ተጠቃሚ እንዲፈጥር ይፈቀድለት?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> በ<xliff:g id="ACCOUNT">%2$s</xliff:g> አዲስ ተጠቃሚ እንዲፈጥር ይፈቀድለት (ይህ መለያ ያለው ተጠቃሚ አስቀድሞ አለ)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ቋንቋ ያክሉ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"የክልል ምርጫ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"የቋንቋ ስም ይተይቡ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index c97d2fa..113a342 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -474,7 +474,7 @@
     <string name="permdesc_transmitIr" product="tablet" msgid="5358308854306529170">"للسماح للتطبيق باستخدام مرسل الأشعة تحت الحمراء الخاص بالجهاز اللوحي."</string>
     <string name="permdesc_transmitIr" product="tv" msgid="2752076865253892198">"‏للسماح للتطبيق باستخدام مُرسِل الأشعة تحت الحمراء في جهاز Android TV."</string>
     <string name="permdesc_transmitIr" product="default" msgid="7957763745020300725">"للسماح للتطبيق باستخدام مرسل الأشعة تحت الحمراء الخاص بالهاتف."</string>
-    <string name="permlab_setWallpaper" msgid="6627192333373465143">"تعيين الخلفية"</string>
+    <string name="permlab_setWallpaper" msgid="6627192333373465143">"ضبط الخلفية"</string>
     <string name="permdesc_setWallpaper" msgid="7373447920977624745">"للسماح للتطبيق بتعيين خلفية النظام."</string>
     <string name="permlab_setWallpaperHints" msgid="3278608165977736538">"تعديل حجم الخلفية"</string>
     <string name="permdesc_setWallpaperHints" msgid="8235784384223730091">"للسماح للتطبيق بتعيين تلميحات حجم خلفية النظام."</string>
@@ -1345,14 +1345,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"انقر للاطلاع على جميع الشبكات"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"جميع الشبكات"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏هل تريد السماح لشبكات Wi‑Fi المقترحة؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"شبكات <xliff:g id="NAME">%s</xliff:g> المقترحة - قد يتم توصيل الجهاز تلقائيًا."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"سماح"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"لا، شكرًا"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏سيتم تشغيل شبكة Wi-Fi تلقائيًا."</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"عندما تكون بالقرب من شبكة محفوظة عالية الجودة"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"عدم إعادة التشغيل"</string>
@@ -2030,8 +2026,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"غير مصنفة"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"لقد عيَّنت أهمية هذه الإشعارات."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"هذه الرسالة مهمة نظرًا لأهمية الأشخاص المعنيين."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"هل تسمح لـ <xliff:g id="APP">%1$s</xliff:g> بإنشاء مستخدم جديد باستخدام <xliff:g id="ACCOUNT">%2$s</xliff:g>؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"هل تسمح لـ <xliff:g id="APP">%1$s</xliff:g> بإنشاء مستخدم جديد باستخدام <xliff:g id="ACCOUNT">%2$s</xliff:g> (يوجد مستخدم بهذا الحساب مسبقًا)؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"إضافة لغة"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"تفضيل المنطقة"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"اكتب اسم اللغة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 2dc3fec..00617a5 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"সকলো নেটৱৰ্ক চাবলৈ টিপক"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"সংযোগ কৰক"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"সকলো নেটৱৰ্ক"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"পৰামর্শ হিচাপে পোৱা নেটৱর্কবোৰক অনুমতি দিবনে?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>এ পৰামর্শ হিচাপে দিয়া নেটৱর্কবোৰ। ডিভাইচটো স্বয়ংক্ৰিয়ভাৱে সংযোগ হ\'ব পাৰে।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"অনুমতি দিয়ক"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"নালাগে, ধন্যবাদ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ৱাই-ফাই স্বয়ংক্ৰিয়ভাৱে অন হ\'ব"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"যেতিয়া আপুনি ছেভ কৰি থোৱা উচ্চ মানৰ নেটৱৰ্কৰ কাষত থাকে"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"পুনৰাই অন নকৰিব"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"শ্ৰেণীবদ্ধ নকৰা"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"এই জাননীবোৰৰ গুৰুত্ব আপুনি ছেট কৰব লাগিব।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"এই কার্যৰ সৈতে জড়িত থকা লোকসকলক ভিত্তি কৰি এইয়া গুৰুত্বপূর্ণ বুলি বিবেচনা কৰা হৈছ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ক <xliff:g id="ACCOUNT">%2$s</xliff:g>ৰ জৰিয়তে নতুন ব্য়ৱহাৰকাৰী সৃষ্টি কৰিবলৈ অনুমতি দিবনে?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>ক <xliff:g id="ACCOUNT">%2$s</xliff:g>ৰ (এই একাউন্টৰ এজন ব্য়ৱহাৰকাৰী ইতিমধ্যে আছে) জৰিয়তে নতুন ব্য়ৱহাৰকাৰী সৃষ্টি কৰিবলৈ অনুমতি দিবনে?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ভাষা যোগ কৰক"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"অঞ্চলৰ অগ্ৰাধিকাৰ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ভাষাৰ নাম লিখক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index af1ca69..f79decd 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Bütün şəbəkələri görmək üçün klikləyin"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Qoşulun"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Bütün şəbəkələr"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Təklif edilən Wi‑Fi şəbəkələrinə icazə verilsin?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> təklif edilən şəbəkə. Cihaz avtomatik qoşula bilər."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"İcazə verin"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Xeyr, təşəkkürlər"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik olaraq aktiv ediləcək"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Yadda saxlanmış yüksək keyfiyyətli şəbəkələr yaxınlıqda olduqda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Yenidən aktiv etməyin"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kateqoriyasız"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bildirişlərin əhəmiyyətini Siz ayarlaryırsınız."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"İnsanlar cəlb olunduğu üçün bu vacibdir."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabı ilə yeni İstifadəçi yaratmağa icazə verilsin?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> tətbiqinə<xliff:g id="ACCOUNT">%2$s</xliff:g> (bu hesab ilə İstifadəçi artıq mövcuddur) hesabı ilə yeni İstifadəçi yaratmağa icazə verilsin?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dil əlavə edin"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region seçimi"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Dil adını daxil edin"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 79717fc..10dc339 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da biste videli sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite da dozvolite predložene Wi‑Fi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dozvoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se automatski uključiti"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne uključuj ponovo"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizovano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi podešavate važnost ovih obaveštenja."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ovo je važno zbog ljudi koji učestvuju."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Želite li da dozvolite aplikaciji <xliff:g id="APP">%1$s</xliff:g> da napravi novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Želite li da dozvolite aplikaciji <xliff:g id="APP">%1$s</xliff:g> da napravi novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g> (korisnik sa ovim nalogom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajte jezik"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Podešavanje regiona"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 7647619..ce65bea 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -992,7 +992,7 @@
     </plurals>
     <string name="last_month" msgid="3959346739979055432">"Апошні месяц"</string>
     <string name="older" msgid="5211975022815554840">"Раней"</string>
-    <string name="preposition_for_date" msgid="9093949757757445117">"дата: <xliff:g id="DATE">%s</xliff:g>"</string>
+    <string name="preposition_for_date" msgid="9093949757757445117">"<xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="preposition_for_time" msgid="5506831244263083793">"у <xliff:g id="TIME">%s</xliff:g>"</string>
     <string name="preposition_for_year" msgid="5040395640711867177">"у <xliff:g id="YEAR">%s</xliff:g>"</string>
     <string name="day" msgid="8144195776058119424">"дзень"</string>
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Дакраніцеся, каб убачыць усе сеткі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Падключыцца"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Усе сеткі"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Дазволіць падключэнне да прапанаваных сетак Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Праграма \"<xliff:g id="NAME">%s</xliff:g>\" прапанавала сеткі. Прылада можа падключыцца да ніх аўтаматычна."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дазволіць"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, дзякуй"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi уключыцца аўтаматычна"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Побач з захаванай сеткай з высакаякасным сігналам"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не ўключаць зноў"</string>
@@ -1393,7 +1389,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"Дазволу не патрабуецца"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"за гэта можа спаганяцца плата"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"ОК"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Зарадка гэтай прылады праз USB"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Прылада зараджаецца праз USB"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"Зарадка падключанай прылады праз USB"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"Перадача файлаў праз USB"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"Перадача фота (PTP) праз USB"</string>
@@ -1724,8 +1720,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> быў адключаны з дапамогай камбінацыі хуткага доступу для спецыяльных магчымасцей"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Каб карыстацца сэрвісам \"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\", націсніце і ўтрымлівайце на працягу трох секунд абедзве клавішы гучнасці"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Выберыце службу для выкарыстання пры націску кнопкі \"Спецыяльныя магчымасці\":"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"З дапамогай жэста спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх) выберыце службу для выкарыстання:"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"З дапамогай жэста спецыяльных магчымасцей (правесці трыма пальцамі па экране знізу ўверх) выберыце службу для выкарыстання:"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберыце службу, дзе будзе выкарыстоўвацца жэст спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберыце службу, дзе будзе выкарыстоўвацца жэст спецыяльных магчымасцей (правесці двума пальцамі па экране знізу ўверх):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Каб пераключыцца на другую службу, націсніце і ўтрымлівайце кнопку спецыяльных магчымасцей."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Каб пераключыцца на другую службу, правядзіце ўверх двума пальцамі, утрымліваючы іх на экране."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Каб пераключыцца на іншую службу, правядзіце ўверх трыма пальцамі, утрымліваючы іх на экране."</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатэгарызаванае"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Вы задалі важнасць гэтых апавяшчэнняў."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Гэта важна, бо з гэтым звязаны пэўныя людзі."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дазволіць <xliff:g id="APP">%1$s</xliff:g> стварыць новага Карыстальніка з уліковым запісам <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дазволіць <xliff:g id="APP">%1$s</xliff:g> стварыць новага Карыстальніка з уліковым запісам <xliff:g id="ACCOUNT">%2$s</xliff:g> (Карыстальнік з гэтым уліковым запісам ужо існуе)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Дадаць мову"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Параметры рэгіёна"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Увядзіце назву мовы"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2c6550d..87ea6de 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризирани"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Зададохте важността на тези известия."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Това е важно заради участващите хора."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Да се разреши ли на <xliff:g id="APP">%1$s</xliff:g> да създаде нов потребител с профила <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Да се разреши ли на <xliff:g id="APP">%1$s</xliff:g> да създаде нов потребител с профила <xliff:g id="ACCOUNT">%2$s</xliff:g> (вече съществува потребител с този профил)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Добавяне на език"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Предпочитание за региона"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Въведете име на език"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index c2d501c..c0843ea 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"এই অ্যাপ আপনার শারীরিক অ্যাক্টিভিটি শনাক্ত করতে পারবে।"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ছবি এবং ভিডিও তোলে"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"এই অ্যাপটি যে কোনো সময় ক্যামেরা ব্যবহার করে ছবি তুলতে বা ভিডিও রেকর্ড করতে পারে৷"</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"সিস্টেম ক্যামেরা ব্যবহার করে ফটো এবং ভিডিও নেওয়ার জন্য অ্যাপ্লিকেশন বা পরিষেবা অ্যাক্সেসের অনুমতি দিন"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"এই প্রিভিলিজ | সিস্টেম অ্যাপটি যেকোনও সময়ে সিস্টেম ক্যামেরা ব্যবহার করে ছবি তুলতে এবং ভিডিও রেকর্ড করতে পারবে। এর জন্য অ্যাপের Android.permission.CAMERA -এর অনুমতি প্রয়োজন"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"ভাইব্রেশন নিয়ন্ত্রণ করুন"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"অ্যাপ্লিকেশানকে কম্পক নিয়ন্ত্রণ করতে দেয়৷"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"সরাসরি ফোন নম্বরগুলিতে কল করে"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"সমস্ত নেটওয়ার্ক দেখতে ট্যাপ করুন"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"সংযুক্ত করুন"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"সব নেটওয়ার্ক"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"সাজেস্ট করা ওয়াই-ফাই নেটওয়ার্কে কানেক্ট করার অনুমতি দিতে চান?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>-এর সাজেস্ট করা নেটওয়ার্ক। ডিভাইস নিজে থেকে কানেক্ট হতে পারে।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"অনুমতি দিন"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"না থাক"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ওয়াই-ফাই অটোমেটিক চালু হবে"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"যখন আপনি একটি উচ্চ মানের সংরক্ষিত নেটওয়ার্ক কাছাকাছি থাকেন"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"আবার চালু করবেন না"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"বিভাগ নির্ধারিত নয়"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"আপনি এই বিজ্ঞপ্তিগুলির গুরুত্ব সেট করেছেন।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"লোকজন জড়িত থাকার কারণে এটি গুরুত্বপূর্ণ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> কে <xliff:g id="ACCOUNT">%2$s</xliff:g> এর সাথে একজন নতুন ব্যবহারকারী তৈরি করার অনুমতি দেবেন কি?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> কে <xliff:g id="ACCOUNT">%2$s</xliff:g> (একজন ব্যবহারকারী এই অ্যাকাউন্টে ইতিমধ্যেই বিদ্যমান আছেন) এর সাথে একজন নতুন ব্যবহারকারী তৈরি করার অনুমতি দেবেন কি?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"একটি ভাষা যোগ করুন"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"পছন্দের অঞ্চল"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ভাষার নাম লিখুন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b28774b..0d4b12c 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -295,7 +295,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da pristupi vašem kalendaru?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"šalje i pregleda SMS poruke"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; slanje i pregled SMS poruka?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da šalje i pregleda SMS poruke?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Pohrana"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"pristupa slikama, medijskim fajlovima i fajlovima na vašem uređaju"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da pristupa fotografijama, medijima i fajlovima na vašem uređaju?"</string>
@@ -1281,14 +1281,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite da vidite sve mreže"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Povežite se"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Dozvoliti predložene WiFi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže <xliff:g id="NAME">%s</xliff:g>. Uređaj će se možda povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dozvoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WiFi će se uključiti automatski"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini sačuvane mreže visokog kvaliteta"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nemoj ponovo uključiti"</string>
@@ -1384,7 +1380,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Punjenje povezanog uređaja. Dodirnite za više opcija."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Otkriven je analogni periferni uređaj"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Priključeni uređaj nije kompatibilan s ovim telefonom. Dodirnite da saznate više."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka putem uređaja spojenog na USB je uspostavljeno"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"Otklanjanje grešaka putem USB-a je uspostavljeno"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"Dodirnite da isključite otklanjanje grešaka putem USB-a"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Odaberite da onemogućite ispravljanje grešaka koristeći USB"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Omogućen način rada okvira za testiranje"</string>
@@ -1930,8 +1926,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nije kategorizirano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi određujete značaj ovih obavještenja."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ovo je značajno zbog osoba koje su uključene."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Da li dozvoljavate aplikaciji <xliff:g id="APP">%1$s</xliff:g> da kreira novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Da li dozvoljavate da <xliff:g id="APP">%1$s</xliff:g> kreira novog korisnika za <xliff:g id="ACCOUNT">%2$s</xliff:g> (Korisnik sa ovim nalogom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajte jezik"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Izbor regije"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Upišite ime jezika"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 6d83936..d828844 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca per veure totes les xarxes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connecta"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Totes les xarxes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vols permetre les xarxes Wi‑Fi suggerides?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Xarxes suggerides de l\'aplicació <xliff:g id="NAME">%s</xliff:g>. El dispositiu pot connectar-se automàticament."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permet"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gràcies"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La Wi-Fi s\'activarà automàticament"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quan siguis a prop d\'una xarxa de qualitat desada"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No tornis a activar"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sense classificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Has definit la importància d\'aquestes notificacions."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Aquest missatge és important per les persones implicades."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Concedeixes permís a <xliff:g id="APP">%1$s</xliff:g> per crear un usuari amb el compte <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Concedeixes permís a <xliff:g id="APP">%1$s</xliff:g> per crear un usuari amb el compte <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Ja hi ha un usuari amb aquest compte.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Afegeix un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferència de regió"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nom de l\'idioma"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 0069804..2b524ae 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všechny sítě"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Připojit"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Všechny sítě"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Povolit navrhované sítě Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Sítě navrhované aplikací <xliff:g id="NAME">%s</xliff:g>. Zařízení se může připojovat automaticky."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Povolit"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, díky"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se zapne automaticky"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Když budete v dosahu kvalitní uložené sítě"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znovu nezapínat"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Neklasifikováno"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Důležitost oznámení určujete vy."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tato zpráva je důležitá kvůli lidem zapojeným do konverzace."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> vytvořit nového uživatele s účtem <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Povolit aplikaci <xliff:g id="APP">%1$s</xliff:g> vytvořit nového uživatele s účtem <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Uživatel s tímto účtem již existuje.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Přidat jazyk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferovaná oblast"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Zadejte název jazyka"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index d12a794..4d57a90 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryk for at se alle netværk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Opret forbindelse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netværk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vil du tillade foreslåede Wi‑Fi-netværk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Netværk foreslået af <xliff:g id="NAME">%s</xliff:g>. Enheden opretter muligvis forbindelse automatisk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillad"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nej tak"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi aktiveres automatisk"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheden af et gemt netværk af høj kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Aktivér ikke igen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uden kategori"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du angiver, hvor vigtige disse notifikationer er."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dette er vigtigt på grund af de personer, det handler om."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vil du give <xliff:g id="APP">%1$s</xliff:g> tilladelse til at oprette en ny bruger med <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vil du give <xliff:g id="APP">%1$s</xliff:g> tilladelse til at oprette en ny bruger med <xliff:g id="ACCOUNT">%2$s</xliff:g> (der findes allerede en bruger med denne konto)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tilføj et sprog"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Områdeindstilling"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Angiv sprog"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index f7b66a1..2ec70d3 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf deine Kontakte zugreift?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Standort"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"auf den Standort deines Geräts zugreifen"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; den Gerätestandort abruft?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"Zulassen, dass die App &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; den Gerätestandort abruft?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"Die App hat nur Zugriff auf den Gerätestandort, solange du sie verwendest"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; &lt;b&gt;ständig&lt;/b&gt; auf deinen Standort zugreift?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"Die App hat gegenwärtig nur dann Zugriff auf den Gerätestandort, wenn du sie verwendest"</string>
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf deinen Kalender zugreift?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS senden und abrufen"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; SMS sendet und aufruft?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Zulassen, dass die App &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; SMS sendet und aufruft?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Speicher"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"auf Fotos, Medien und Dateien auf deinem Gerät zugreifen"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Zulassen, dass &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; auf Fotos, Medien und Dateien auf deinem Gerät zugreift?"</string>
@@ -434,8 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"Diese App kann deine körperliche Aktivität erkennen."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"Diese App kann mit der Kamera jederzeit Bilder und Videos aufnehmen."</string>
-    <string name="permlab_systemCamera" msgid="4074081285026193898">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufzunehmen"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Diese privilegierte System-App kann jederzeit mit einer System-Kamera Bilder und Videos aufnehmen. Sie benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string>
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"Einer App oder einem Dienst Zugriff auf Systemkameras erlauben, um Fotos und Videos aufnehmen zu können"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Diese privilegierte App | System-App kann jederzeit mit einer Systemkamera Bilder und Videos aufnehmen. Die App benötigt auch die Berechtigung \"android.permission.CAMERA\"."</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"Vibrationsalarm steuern"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Ermöglicht der App, den Vibrationsalarm zu steuern"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"Telefonnummern direkt anrufen"</string>
@@ -999,7 +999,7 @@
     <string name="weeks" msgid="6509623834583944518">"Wochen"</string>
     <string name="year" msgid="4001118221013892076">"Jahr"</string>
     <string name="years" msgid="6881577717993213522">"Jahre"</string>
-    <string name="now_string_shortest" msgid="8912796667087856402">"jetzt"</string>
+    <string name="now_string_shortest" msgid="8912796667087856402">"Jetzt"</string>
     <plurals name="duration_minutes_shortest" formatted="false" msgid="3957499975064245495">
       <item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="COUNT_0">%d</xliff:g> min</item>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tippen, um alle Netzwerke zu sehen"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinden"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle Netzwerke"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vorgeschlagene WLANs zulassen?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Von <xliff:g id="NAME">%s</xliff:g> vorgeschlagene Netzwerke. Gerät verbindet sich möglicherweise automatisch."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Zulassen"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nein danke"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN wird automatisch aktiviert"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wenn du in der Nähe eines sicheren gespeicherten Netzwerks bist"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nicht wieder aktivieren"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Unkategorisiert"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du hast die Wichtigkeit dieser Benachrichtigungen festgelegt."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Diese Benachrichtigung ist aufgrund der beteiligten Personen wichtig."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Möchtest du zulassen, dass <xliff:g id="APP">%1$s</xliff:g> einen neuen Nutzer mit <xliff:g id="ACCOUNT">%2$s</xliff:g> erstellt?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Möchtest du zulassen, dass <xliff:g id="APP">%1$s</xliff:g> einen neuen Nutzer mit <xliff:g id="ACCOUNT">%2$s</xliff:g> erstellt? Dieses Konto wird jedoch bereits von einem anderen Nutzer verwendet."</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Sprache hinzufügen"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region auswählen"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sprache eingeben"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 30824c8..6998c0c 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Πατήστε για να δείτε όλα τα δίκτυα"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Σύνδεση"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Όλα τα δίκτυα"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Να επιτρέπονται προτεινόμενα δίκτυα Wi‑Fi;"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Προτεινόμενα δίκτυα <xliff:g id="NAME">%s</xliff:g>. Η συσκευή μπορεί να συνδεθεί αυτόματα."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Αποδοχή"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Όχι, ευχαριστώ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Το Wi‑Fi θα ενεργοποιηθεί αυτόματα"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Όταν βρίσκεστε κοντά σε αποθηκευμένο δίκτυο υψηλής ποιότητας"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Να μην ενεργοποιηθεί ξανά"</string>
@@ -1679,7 +1675,7 @@
     <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Επιλέξτε μια υπηρεσία που θα χρησιμοποιείται με την κίνηση προσβασιμότητας (σύρετε με δύο δάχτυλα προς τα επάνω από το κάτω μέρος της οθόνης):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Επιλέξτε μια υπηρεσία που θα χρησιμοποιείται με την κίνηση προσβασιμότητας (σύρετε με τρία δάχτυλα προς τα επάνω από το κάτω μέρος της οθόνης):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Για εναλλαγή μεταξύ υπηρεσιών, αγγίξτε παρατεταμένα το κουμπί προσβασιμότητας."</string>
-    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με δύο δάχτυλα προς τα επάνω."</string>
+    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με δύο δάχτυλα προς τα επάνω και μην τα απομακρύνετε από την οθόνη."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Για εναλλαγή μεταξύ υπηρεσιών, σύρετε παρατεταμένα με τρία δάχτυλα προς τα επάνω."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Μεγιστοποίηση"</string>
     <string name="user_switched" msgid="3768006783166984410">"Τρέχων χρήστης <xliff:g id="NAME">%1$s</xliff:g>."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Μη κατηγοριοποιημένο"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Μπορείτε να ρυθμίσετε τη βαρύτητα αυτών των ειδοποιήσεων."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Αυτό είναι σημαντικό λόγω των ατόμων που συμμετέχουν."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Να επιτραπεί στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με το λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g>;"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Να επιτραπεί στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με το λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> (υπάρχει ήδη χρήστης με αυτόν το λογαριασμό);"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Προσθήκη γλώσσας"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Προτίμηση περιοχής"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Εισαγ. όνομα γλώσσας"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 839aaac..6da3a5f 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 2db3771..7c2b329 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -554,10 +554,10 @@
   <string-array name="fingerprint_error_vendor">
   </string-array>
     <string name="fingerprint_icon_content_description" msgid="2340202869968465936">"Fingerprint icon"</string>
-    <string name="permlab_manageFace" msgid="7262837876352591553">"manage Face Unlock hardware"</string>
+    <string name="permlab_manageFace" msgid="7262837876352591553">"manage face unlock hardware"</string>
     <string name="permdesc_manageFace" msgid="8919637120670185330">"Allows the app to invoke methods to add and delete facial templates for use."</string>
-    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use Face Unlock hardware"</string>
-    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use Face Unlock hardware for authentication"</string>
+    <string name="permlab_useFaceAuthentication" msgid="2565716575739037572">"use face unlock hardware"</string>
+    <string name="permdesc_useFaceAuthentication" msgid="4712947955047607722">"Allows the app to use face unlock hardware for authentication"</string>
     <string name="face_recalibrate_notification_name" msgid="1913676850645544352">"Face unlock"</string>
     <string name="face_recalibrate_notification_title" msgid="4087620069451499365">"Re-enrol your face"</string>
     <string name="face_recalibrate_notification_content" msgid="5530308842361499835">"To improve recognition, please re-enrol your face"</string>
@@ -584,15 +584,15 @@
   <string-array name="face_acquired_vendor">
   </string-array>
     <string name="face_error_hw_not_available" msgid="396883585636963908">"Can’t verify face. Hardware not available."</string>
-    <string name="face_error_timeout" msgid="981512090365729465">"Try Face Unlock again."</string>
+    <string name="face_error_timeout" msgid="981512090365729465">"Try face unlock again."</string>
     <string name="face_error_no_space" msgid="2712120617457553825">"Can’t store new face data. Delete an old one first."</string>
     <string name="face_error_canceled" msgid="283945501061931023">"Face operation cancelled."</string>
-    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face Unlock cancelled by user."</string>
+    <string name="face_error_user_canceled" msgid="5317030072349668946">"Face unlock cancelled by user."</string>
     <string name="face_error_lockout" msgid="3407426963155388504">"Too many attempts. Try again later."</string>
-    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face Unlock disabled."</string>
+    <string name="face_error_lockout_permanent" msgid="4723594314443097159">"Too many attempts. Face unlock disabled."</string>
     <string name="face_error_unable_to_process" msgid="4940944939691171539">"Can’t verify face. Try again."</string>
-    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up Face Unlock."</string>
-    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face Unlock is not supported on this device."</string>
+    <string name="face_error_not_enrolled" msgid="4016937174832839540">"You haven’t set up face unlock."</string>
+    <string name="face_error_hw_not_present" msgid="8302690289757559738">"Face unlock is not supported on this device."</string>
     <string name="face_name_template" msgid="7004562145809595384">"Face <xliff:g id="FACEID">%d</xliff:g>"</string>
   <string-array name="face_error_vendor">
   </string-array>
@@ -821,7 +821,7 @@
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Try again"</string>
     <string name="lockscreen_password_wrong" msgid="5737815393253165301">"Try again"</string>
     <string name="lockscreen_storage_locked" msgid="9167551160010625200">"Unlock for all features and data"</string>
-    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum Face Unlock attempts exceeded"</string>
+    <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Maximum face unlock attempts exceeded"</string>
     <string name="lockscreen_missing_sim_message_short" msgid="5099439277819215399">"No SIM card"</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No SIM card in tablet."</string>
     <string name="lockscreen_missing_sim_message" product="tv" msgid="3360993527792167595">"No SIM card in your Android TV device."</string>
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uncategorised"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"You set the importance of these notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"This is important because of the people involved."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Add a language"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Region preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Type language name"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 7961a86..62f294e 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -1890,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‎Uncategorized‎‏‎‎‏‎"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‎You set the importance of these notifications.‎‏‎‎‏‎"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎This is important because of the people involved.‎‏‎‎‏‎"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to create a new User with ‎‏‎‎‏‏‎<xliff:g id="ACCOUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎ ?‎‏‎‎‏‎"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to create a new User with ‎‏‎‎‏‏‎<xliff:g id="ACCOUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎ (a User with this account already exists) ?‎‏‎‎‏‎"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‎‎‎‎‎‎Add a language‎‏‎‎‏‎"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎Region preference‎‏‎‎‏‎"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‏‏‎‎Type language name‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index ac6a6ce..7346bd0 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -30,7 +30,7 @@
     <string name="untitled" msgid="4638956954852782576">"&lt;Sin título&gt;"</string>
     <string name="emptyPhoneNumber" msgid="7694063042079676517">"(No hay número de teléfono)"</string>
     <string name="unknownName" msgid="6867811765370350269">"Desconocido"</string>
-    <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string>
+    <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string>
     <string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string>
@@ -944,7 +944,7 @@
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="8497389531014185509">"Permite que la aplicación modifique el historial o los favoritos del navegador almacenados en el dispositivo. La aplicación puede utilizar este permiso para borrar o modificar los datos del navegador. Nota: Este permiso no puede ser utilizado por navegadores externos ni otras aplicaciones que tengan funciones de navegación por Internet."</string>
     <string name="permlab_setAlarm" msgid="1379294556362091814">"programar una alarma"</string>
     <string name="permdesc_setAlarm" msgid="316392039157473848">"Permite que la aplicación establezca una alarma en una aplicación de alarma instalada. Es posible que algunas aplicaciones de alarma no incluyan esta función."</string>
-    <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar correo de voz"</string>
+    <string name="permlab_addVoicemail" msgid="5525660026090959044">"agregar buzón de voz"</string>
     <string name="permdesc_addVoicemail" msgid="6604508651428252437">"Permite que la aplicación agregue mensajes a la bandeja de entrada de tu buzón de voz."</string>
     <string name="permlab_writeGeolocationPermissions" msgid="5962224158955273932">"Modificar los permisos de ubicación geográfica del navegador"</string>
     <string name="permdesc_writeGeolocationPermissions" msgid="1083743234522638747">"Permite que la aplicación modifique los permisos de ubicación geográfica del navegador. Las aplicaciones maliciosas pueden utilizar esto para permitir el envío de información de ubicación a sitios web arbitrarios."</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Presiona para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas las redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"¿Quieres permitir las redes Wi‑Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> sugirió redes. Es posible que el dispositivo se conecte automáticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gracias"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Se activará la conexión Wi-Fi automáticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red guardada de alta calidad"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sin categoría"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Estableciste la importancia de estas notificaciones."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Es importante debido a las personas involucradas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"¿Quieres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario nuevo con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"¿Quieres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario nuevo con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Ya existe un usuario con esta cuenta)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Agregar un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nombre del idioma"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 059b0a20..6c1c992 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas las redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas las redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"¿Permitir sugerencias de redes Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> te ha sugerido alguna red. El dispositivo puede que se conecte automáticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, gracias"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"La conexión Wi‑Fi se activará automáticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cuando estés cerca de una red de alta calidad guardada"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"No volver a activar"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Se ha detectado un accesorio de audio analógico"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"El dispositivo adjunto no es compatible con este teléfono. Toca para obtener más información."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuración USB habilitada"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toca para desactivar la depuración USB."</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Tocar para desactivar depuración USB"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Seleccionar para inhabilitar la depuración USB"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Modo de agente de prueba habilitado"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Restablece los ajustes de fábrica para inhabilitar el modo de agente de prueba."</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"El acceso directo a accesibilidad ha desactivado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Para utilizar <xliff:g id="SERVICE_NAME">%1$s</xliff:g>, mantén pulsadas ambas teclas de volumen durante 3 segundos"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Selecciona el servicio que se utilizará cuando toques el botón Accesibilidad:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Elige el servicio que se utilizará con el gesto de accesibilidad (desliza dos dedos hacia arriba desde la parte inferior de la pantalla):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Elige el servicio que se utilizará con el gesto de accesibilidad (desliza tres dedos hacia arriba desde la parte inferior de la pantalla):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Elige el servicio que se utilizará con el gesto de accesibilidad (deslizar dos dedos hacia arriba desde la parte inferior de la pantalla):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Elige el servicio que se utilizará con el gesto de accesibilidad (deslizar tres dedos hacia arriba desde la parte inferior de la pantalla):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para cambiar de un servicio a otro, mantén pulsado el botón de accesibilidad."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para cambiar de un servicio a otro, desliza dos dedos hacia arriba y mantén pulsada la pantalla."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para cambiar de un servicio a otro, desliza tres dedos hacia arriba y mantén pulsada la pantalla."</string>
@@ -1894,9 +1890,11 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sin clasificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Tú determinas la importancia de estas notificaciones."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Esto es importante por los usuarios implicados."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"¿Permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario con la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"¿Permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario con la cuenta <xliff:g id="ACCOUNT">%2$s</xliff:g> (ya existe un usuario con esta cuenta)?"</string>
-    <string name="language_selection_title" msgid="2680677278159281088">"Añade un idioma"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
+    <string name="language_selection_title" msgid="2680677278159281088">"Añadir un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nombre de idioma"</string>
     <string name="language_picker_section_suggested" msgid="8414489646861640885">"Sugeridos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index f7601fd..28d1203 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -243,7 +243,7 @@
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Hääletu režiim"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Heli on VÄLJAS"</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Heli on SEES"</string>
-    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Lennurežiim"</string>
+    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Lennukirežiim"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Lennurežiim on SEES"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Lennurežiim on VÄLJAS"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Seaded"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Puudutage kõikide võrkude nägemiseks"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ühenda"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Kõik võrgud"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Kas lubada soovitatud WiFi-võrgud?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rakenduse <xliff:g id="NAME">%s</xliff:g> soovitatud võrgud. Seade võib automaatselt ühenduse luua."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Luba"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Tänan, ei"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WiFi lülitub sisse automaatselt"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kui olete kvaliteetse salvestatud võrgu läheduses"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ära lülita tagasi sisse"</string>
@@ -1676,7 +1672,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Juurdepääsetavuse otsetee lülitas teenuse <xliff:g id="SERVICE_NAME">%1$s</xliff:g> välja"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Teenuse <xliff:g id="SERVICE_NAME">%1$s</xliff:g> kasutamiseks hoidke kolm sekundit all mõlemat helitugevuse klahvi"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Valige, millist teenust kasutada, kui puudutate juurdepääsetavuse nuppu:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (pühkige kahe sõrmega ekraanikuval alt üles):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (kahe sõrmega ekraanikuval alt üles pühkimine):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Valige, millist teenust kasutada koos juurdepääsetavuse liigutusega (kolme sõrmega ekraanikuval alt üles pühkimine):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Teenuste vahel vahetamiseks vajutage pikalt juurdepääsetavuse nuppu."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Teenuste vahel vahetamiseks pühkige kahe sõrmega üles ja hoidke."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategoriseerimata"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Teie määrasite nende märguannete tähtsuse."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"See on tähtis osalevate inimeste tõttu."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Kas lubada rakendusel <xliff:g id="APP">%1$s</xliff:g> luua uus kasutaja kontoga <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Kas lubada rakendusel <xliff:g id="APP">%1$s</xliff:g> luua uus kasutaja kontoga <xliff:g id="ACCOUNT">%2$s</xliff:g> (selle kontoga kasutaja on juba olemas)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Keele lisamine"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Piirkonnaeelistus"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sisestage keele nimi"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2cfda07..9460ee7 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Sakatu hau sare guztiak ikusteko"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Konektatu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sare guztiak"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Iradokitako wifi-sareak baimendu nahi dituzu?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> aplikazioak sare batzuk iradoki ditu. Baliteke gailua automatikoki konektatzea."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Baimendu"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ez. Eskerrik asko."</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi konexioa automatikoki aktibatuko da"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gordeta daukazun kalitate handiko sare batetik gertu zaudenean"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ez aktibatu berriro"</string>
@@ -1642,7 +1638,7 @@
     <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"PIN kode okerra."</string>
     <string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Idatzi 4 eta 8 zenbaki arteko PINa."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6025069204539532000">"PUK kodeak 8 zenbaki izan behar ditu."</string>
-    <string name="kg_invalid_puk" msgid="3638289409676051243">"Idatzi berriro PUK kode zuzena. Hainbat saiakera oker eginez gero, betirako desgaituko da SIMa."</string>
+    <string name="kg_invalid_puk" msgid="3638289409676051243">"Idatzi berriro PUK kode zuzena. Hainbat saiakera oker eginez gero, betiko desgaituko da SIMa."</string>
     <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodeak ez datoz bat"</string>
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Eredua marrazteko saiakera gehiegi egin dira"</string>
     <string name="kg_login_instructions" msgid="1100551261265506448">"Desblokeatzeko, hasi saioa Google kontuarekin."</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategoriarik gabea"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Zuk ezarri duzu jakinarazpen hauen garrantzia."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Garrantzitsua da eragiten dien pertsonengatik."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> aplikazioari <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua duen erabiltzailea sortzeko baimena eman nahi diozu?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> aplikazioari <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua duen erabiltzailea sortzeko baimena eman nahi diozu? (Badago kontu hori duen erabiltzaile bat)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Gehitu hizkuntza"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Lurralde-hobespena"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Adierazi hizkuntza"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index c620ab2..ec5dbcb2 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه داده شود به تقویم شما دسترسی پیدا کند؟"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"پیامک"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"ارسال و مشاهده پیامک‌ها"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; اجازه داده شود پیامک‌ها را ارسال و مشاهده کند؟"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"‏به «&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt;» اجازه داده شود پیامک ارسال و مشاهده کند؟"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"حافظه"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"دسترسی به عکس‌ها، رسانه‌ها و فایل‌های روی دستگاهتان"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"‏به برنامه &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; اجازه داده شود به عکس‌ها، رسانه، و فایل‌های موجود در دستگاهتان دسترسی داشته باشد؟"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"برای دیدن همه شبکه‌ها ضربه بزنید"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"اتصال"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"همه شبکه‌ها"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏شبکه‌های Wi‑Fi پیشنهادی مجاز شود؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"شبکه‌های پیشنهادی <xliff:g id="NAME">%s</xliff:g>. ممکن است دستگاه به‌طور خودکار متصل شود."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"مجاز"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"نه متشکرم"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi به‌طور خودکار روشن خواهد شد"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"وقتی نزدیک شبکه ذخیره‌شده با کیفیت بالا هستید"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوباره روشن نشود"</string>
@@ -1814,8 +1810,8 @@
     <string name="package_updated_device_owner" msgid="1847154566357862089">"توسط سرپرست سیستم به‌روزرسانی شد"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"توسط سرپرست سیستم حذف شد"</string>
     <string name="confirm_battery_saver" msgid="639106420541753635">"تأیید"</string>
-    <string name="battery_saver_description_with_learn_more" msgid="2108984221113106294">"بهینه‌سازی باتری فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های با مصرف بالای نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد. "<annotation id="url">"بیشتر بدانید"</annotation></string>
-    <string name="battery_saver_description" msgid="6413346684861241431">"بهینه‌سازی باتری فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های با مصرف بالای نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد."</string>
+    <string name="battery_saver_description_with_learn_more" msgid="2108984221113106294">"«بهینه‌سازی باتری» فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های پرمصرف نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد. "<annotation id="url">"بیشتر بدانید"</annotation></string>
+    <string name="battery_saver_description" msgid="6413346684861241431">"«بهینه‌سازی باتری» فعالیت پس‌زمینه، برخی جلوه‌های دیداری، و سایر ویژگی‌های پرمصرف نیرو را خاموش یا محدود می‌کند تا عمر باتری افزایش یابد."</string>
     <string name="data_saver_description" msgid="6015391409098303235">"برای کمک به کاهش مصرف داده، «صرفه‌جویی داده» از ارسال و دریافت داده در پس‌زمینه ازطرف بعضی برنامه‌ها جلوگیری می‌کند. برنامه‌ای که درحال‌حاضر استفاده می‌کنید می‌تواند به داده‌ها دسترسی داشته باشد اما دفعات دسترسی آن محدود است.این یعنی، برای مثال، تصاویر تا زمانی که روی آن‌ها ضربه نزنید نشان داده نمی‌شوند."</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"صرفه‌جویی داده روشن شود؟"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"روشن کردن"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"دسته‌بندی‌نشده"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"شما اهمیت این اعلان‌ها را تنظیم می‌کنید."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"به دلیل افراد درگیر مهم است."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"به <xliff:g id="APP">%1$s</xliff:g> امکان داده شود کاربر جدیدی با <xliff:g id="ACCOUNT">%2$s</xliff:g> اضافه کند؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"به <xliff:g id="APP">%1$s</xliff:g> امکان داده شود کاربر جدیدی با <xliff:g id="ACCOUNT">%2$s</xliff:g> ایجاد کند (کاربری با این حساب از قبل وجود دارد)؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"افزودن زبان"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"اولویت‌های منطقه"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"نام زبان را تایپ کنید"</string>
@@ -2010,15 +2008,15 @@
     <string name="notification_appops_overlay_active" msgid="633813008357934729">"نمایش روی برنامه‌های دیگر در صفحه‌نمایش"</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2348803891571320452">"اعلان اطلاعات حالت روال معمول"</string>
     <string name="dynamic_mode_notification_title" msgid="508815255807182035">"ممکن است شارژ باتری قبل از شارژ معمول تمام شود"</string>
-    <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"جهت افزایش عمر باتری، بهینه‌سازی باتری فعال شد"</string>
+    <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"جهت افزایش عمر باتری، «بهینه‌سازی باتری» فعال شد"</string>
     <string name="battery_saver_notification_channel_name" msgid="2083316159716201806">"بهینه‌سازی باتری"</string>
-    <string name="battery_saver_sticky_disabled_notification_title" msgid="6376147579378764641">"تا وقتی شارژ باتری دوباره به سطح پایین نرسد، بهینه‌سازی باتری مجدداً فعال نخواهد شد"</string>
-    <string name="battery_saver_sticky_disabled_notification_summary" msgid="8090192609249817945">"باتری تا سطحی کافی شارژ شده است. تا وقتی شارژ باتری دوباره به سطح پایین نرسد، بهینه‌سازی باتری مجدداً فعال نخواهد شد."</string>
+    <string name="battery_saver_sticky_disabled_notification_title" msgid="6376147579378764641">"تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد"</string>
+    <string name="battery_saver_sticky_disabled_notification_summary" msgid="8090192609249817945">"باتری درحد کافی شارژ شده است. تا وقتی شارژ باتری دوباره به سطح پایین نرسد، «بهینه‌سازی باتری» مجدداً فعال نخواهد شد."</string>
     <string name="battery_saver_charged_notification_title" product="default" msgid="2960978289873161288">"تلفن <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
     <string name="battery_saver_charged_notification_title" product="tablet" msgid="7555713825806482451">"رایانه لوحی <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
     <string name="battery_saver_charged_notification_title" product="device" msgid="5954873381559605660">"دستگاه <xliff:g id="CHARGE_LEVEL">%1$s</xliff:g> شارژ شد"</string>
-    <string name="battery_saver_off_notification_summary" msgid="1374222493681267143">"بهینه‌سازی باتری خاموش است. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
-    <string name="battery_saver_off_alternative_notification_summary" msgid="4340727818546508436">"بهینه‌سازی باتری خاموش شد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
+    <string name="battery_saver_off_notification_summary" msgid="1374222493681267143">"«بهینه‌سازی باتری» خاموش است. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
+    <string name="battery_saver_off_alternative_notification_summary" msgid="4340727818546508436">"«بهینه‌سازی باتری» خاموش شد. ویژگی‌ها دیگر محدود نمی‌شوند."</string>
     <string name="mime_type_folder" msgid="7111951698626315204">"پوشه"</string>
     <string name="mime_type_apk" msgid="5518003630972506900">"‏برنامه Android"</string>
     <string name="mime_type_generic" msgid="6833871596845900027">"فایل"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 5b934ea3..a94253b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; yhteystietojesi käyttöoikeuden?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Sijainti"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"käyttää laitteen sijaintia"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tämän laitteen sijainnin käyttöoikeuden?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; oikeuden nähdä tämän laitteen sijainnin?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"Sovellus saa sijainnin käyttöoikeuden vain silloin, kun käytät sovellusta"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; käyttää laitteen sijaintia &lt;b&gt;aina&lt;/b&gt;?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"Sovellus saa tällä hetkellä sijainnin käyttöoikeuden vain, jos käytät sovellusta"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Napauta, niin näet kaikki verkot."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Yhdistä"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Kaikki verkot"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Sallitaanko ehdotetut Wi-Fi-verkot?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ehdotti verkkoja. Laite voi muodostaa yhteyden automaattisesti."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Salli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ei kiitos"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi käynnistyy automaattisesti"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kun olet lähellä laadukasta tallennettua verkkoa"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Älä käynnistä uudelleen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Luokittelematon"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Voit valita näiden ilmoitusten tärkeyden."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tämä on tärkeää siihen liittyvien ihmisten perusteella."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Myönnetäänkö sovellukselle <xliff:g id="APP">%1$s</xliff:g> oikeus luoda käyttäjä tilille <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Myönnetäänkö sovellukselle <xliff:g id="APP">%1$s</xliff:g> oikeus luoda käyttäjä tilille <xliff:g id="ACCOUNT">%2$s</xliff:g> (tilillä on jo käyttäjä)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Lisää kieli"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Alueasetus"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Anna kielen nimi"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index b4dfde6..94f6acb 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -100,7 +100,7 @@
     <string name="peerTtyModeHco" msgid="5728602160669216784">"Mode TTY HCO demandé par un pair"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"Mode TTY VCO demandé par un pair"</string>
     <string name="peerTtyModeOff" msgid="3280819717850602205">"Mode TTY DÉSACTIVÉ demandé par un pair"</string>
-    <string name="serviceClassVoice" msgid="1258393812335258019">"Google Voice"</string>
+    <string name="serviceClassVoice" msgid="1258393812335258019">"Voix"</string>
     <string name="serviceClassData" msgid="872456782077937893">"Données"</string>
     <string name="serviceClassFAX" msgid="5566624998840486475">"Télécopie"</string>
     <string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Touchez pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connexion"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tous les réseaux"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Autoriser les suggestions de réseaux Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil peut s\'y connecter automatiquement."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Autoriser"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non merci"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi s\'activera automatiquement"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes près d\'un réseau enregistré de haute qualité"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
@@ -1676,10 +1672,10 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Le raccourci d\'accessibilité a désactivé la fonction <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Maintenez enfoncées les deux touches de volume pendant trois secondes pour utiliser <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Choisissez un service à utiliser lorsque vous touchez le bouton d\'accessibilité :"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer deux doigts du bas de l\'écran vers le haut) :"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer trois doigts du bas de l\'écran vers le haut) :"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer l\'écran de bas en haut avec deux doigts) :"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Choisissez un service à utiliser lorsque vous utilisez le geste d\'accessibilité (balayer l\'écran de bas en haut avec trois doigts) :"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Pour basculer entre les services, maintenez le doigt sur le bouton d\'accessibilité."</string>
-    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Pour basculer entre les services, balayez deux doigts vers le haut et maintenez-les sur l\'écran."</string>
+    <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Pour basculer entre les services, balayez l\'écrfan vers le haut avec deux doigts et maintenez-les-y."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Pour changer de service, balayez trois doigts vers le haut et maintenez-les sur l\'écran."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Zoom"</string>
     <string name="user_switched" msgid="3768006783166984410">"Utilisateur actuel : <xliff:g id="NAME">%1$s</xliff:g>"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sans catégorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vous définissez l\'importance de ces notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ces notifications sont importantes en raison des participants."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil d\'utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil d\'utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Un utilisateur associé à ce compte existe déjà.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ajouter une langue"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Préférences régionales"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Entrez la langue"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 33d13dc..11ba7dc 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; d\'accéder à votre agenda ?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"envoyer et consulter des SMS"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; d\'envoyer et d\'afficher des SMS ?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Autoriser l\'application &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à envoyer et afficher des SMS ?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Stockage"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"accéder aux photos, contenus multimédias et fichiers sur votre appareil"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Autoriser l\'appli &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux photos, contenus multimédias et fichiers sur votre appareil ?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Appuyer pour afficher tous les réseaux"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Se connecter"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tous les réseaux"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Autoriser les suggestions de réseaux Wi‑Fi ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Réseaux suggérés par <xliff:g id="NAME">%s</xliff:g>. L\'appareil pourra se connecter automatiquement."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Autoriser"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non, merci"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Le Wi-Fi sera activé automatiquement"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Lorsque vous êtes à proximité d\'un réseau enregistré de haute qualité"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne pas réactiver"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sans catégorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vous définissez l\'importance de ces notifications."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ces notifications sont importantes en raison des participants."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Autoriser <xliff:g id="APP">%1$s</xliff:g> à créer un profil utilisateur avec le compte <xliff:g id="ACCOUNT">%2$s</xliff:g> (un utilisateur associé à ce compte existe déjà) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ajouter une langue"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Préférences régionales"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Saisissez la langue"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index f977c45..1becada 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1094,27 +1094,27 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de introdución de texto"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
-    <string name="email_desc" msgid="3638665569546416795">"Envía un correo electrónico ao enderezo seleccionado"</string>
+    <string name="email_desc" msgid="3638665569546416795">"Enviar un correo electrónico ao enderezo seleccionado"</string>
     <string name="dial" msgid="1253998302767701559">"Chamar"</string>
-    <string name="dial_desc" msgid="6573723404985517250">"Chama ao número de teléfono seleccionado"</string>
+    <string name="dial_desc" msgid="6573723404985517250">"Chamar ao número de teléfono seleccionado"</string>
     <string name="map" msgid="5441053548030107189">"Mapa"</string>
-    <string name="map_desc" msgid="1836995341943772348">"Localiza o enderezo seleccionado"</string>
+    <string name="map_desc" msgid="1836995341943772348">"Localizar o enderezo seleccionado"</string>
     <string name="browse" msgid="1245903488306147205">"Abrir"</string>
-    <string name="browse_desc" msgid="8220976549618935044">"Abre o URL seleccionado"</string>
+    <string name="browse_desc" msgid="8220976549618935044">"Abrir o URL seleccionado"</string>
     <string name="sms" msgid="4560537514610063430">"Enviar SMS"</string>
-    <string name="sms_desc" msgid="7526588350969638809">"Envía unha mensaxe ao número de teléfono seleccionado"</string>
+    <string name="sms_desc" msgid="7526588350969638809">"Enviar unha mensaxe ao número de teléfono seleccionado"</string>
     <string name="add_contact" msgid="7867066569670597203">"Engadir"</string>
-    <string name="add_contact_desc" msgid="4830217847004590345">"Engade o elemento aos contactos"</string>
+    <string name="add_contact_desc" msgid="4830217847004590345">"Engadir o elemento aos contactos"</string>
     <string name="view_calendar" msgid="979609872939597838">"Ver"</string>
-    <string name="view_calendar_desc" msgid="5828320291870344584">"Consulta a hora seleccionada no calendario"</string>
+    <string name="view_calendar_desc" msgid="5828320291870344584">"Consultar a hora seleccionada no calendario"</string>
     <string name="add_calendar_event" msgid="1953664627192056206">"Programar"</string>
-    <string name="add_calendar_event_desc" msgid="4326891793260687388">"Programa un evento para a data seleccionada"</string>
+    <string name="add_calendar_event_desc" msgid="4326891793260687388">"Programar un evento para a data seleccionada"</string>
     <string name="view_flight" msgid="7691640491425680214">"Realizar seguimento"</string>
-    <string name="view_flight_desc" msgid="3876322502674253506">"Fai un seguimento do voo seleccionado"</string>
+    <string name="view_flight_desc" msgid="3876322502674253506">"Facer un seguimento do voo seleccionado"</string>
     <string name="translate" msgid="9218619809342576858">"Traducir"</string>
-    <string name="translate_desc" msgid="4502367770068777202">"Traduce o texto seleccionado"</string>
+    <string name="translate_desc" msgid="4502367770068777202">"Traducir o texto seleccionado"</string>
     <string name="define" msgid="7394820043869954211">"Definir"</string>
-    <string name="define_desc" msgid="7910883642444919726">"Define o texto seleccionado"</string>
+    <string name="define_desc" msgid="7910883642444919726">"Definir o texto seleccionado"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Estase esgotando o espazo de almacenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"É posible que algunhas funcións do sistema non funcionen"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Non hai almacenamento suficiente para o sistema. Asegúrate de ter un espazo libre de 250 MB e reinicia o dispositivo."</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toca para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectarse"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Queres permitir as redes wifi suxeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes suxeridas de <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode conectarse automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Non, grazas"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A wifi activarase automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Cando esteas preto dunha rede gardada de alta calidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non volver activar"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sen clasificar"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ti defines a importancia destas notificacións."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"É importante polas persoas involucradas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Queres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario novo con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Queres permitir que <xliff:g id="APP">%1$s</xliff:g> cree un usuario novo con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Xa existe un usuario con esta conta)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Engadir un idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferencia de rexión"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nome do idioma"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 83e94a1..09661c1 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને તમારા સંપર્કોને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"સ્થાન"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"આ ઉપકરણના સ્થાનને ઍક્સેસ કરવાની"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ઉપકરણના સ્થાનને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસના સ્થાનને ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"જ્યારે તમે ઍપનો ઉપયોગ કરી રહ્યા હશો માત્ર ત્યારે જ ઍપ સ્થાનને ઍક્સેસ કરી શકશે"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસનું સ્થાન &lt;b&gt;હંમેશાં&lt;/b&gt; ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"હાલમાં માત્ર જ્યારે તમે ઍપનો ઉપયોગ કરી રહ્યા હશો હોય ત્યારે જ ઍપ સ્થાનને ઍક્સેસ કરી શકશે"</string>
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"આ ઍપ તમારી શારીરિક પ્રવૃત્તિને ઓળખી શકે છે."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ચિત્રો અને વિડિઓઝ લો"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"આ ઍપ્લિકેશન, કૅમેરાનો ઉપયોગ કરીને કોઈપણ સમયે ચિત્રો લઈ અને વિડિઓઝ રેકોર્ડ કરી શકે છે."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ઍપ્લિકેશન અથવા સેવા ઍક્સેસને સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા અને વીડિયો લેવાની મંજૂરી આપો"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"આ વિશેષાધિકૃત | સિસ્ટમ ઍપ કોઈપણ સમયે સિસ્ટમ કૅમેરાનો ઉપયોગ કરીને ફોટા લઈ અને વીડિયો રેકોર્ડ કરી શકે છે. ઍપ દ્વારા આયોજિત કરવા માટે android.permission.CAMERAની પરવાનગી પણ જરૂરી છે"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"વાઇબ્રેશન નિયંત્રિત કરો"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"એપ્લિકેશનને વાઇબ્રેટરને નિયંત્રિત કરવાની મંજૂરી આપે છે."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"સીધા જ ફોન નંબર્સ પર કૉલ કરો"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"બધા નેટવર્ક જોવા ટૅપ કરો"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"કનેક્ટ કરો"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"બધા નેટવર્કો"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"સૂચવેલા વાઇ-ફાઇ નેટવર્કને મંજૂરી આપીએ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> સૂચવેલા નેટવર્ક. ડિવાઇસ ઑટોમૅટિક રીતે કનેક્ટ થાય તેમ બની શકે છે."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"મંજૂરી આપો"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ના, આભાર"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"વાઇ-ફાઇ આપમેળે ચાલુ થઈ જશે"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"જ્યારે તમે એક ઉચ્ચ ક્વૉલિટીવાળા સાચવેલ નેટવર્કની નજીક હોવ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"પાછું ચાલુ કરશો નહીં"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"અવર્ગીકૃત"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"તમે આ સૂચનાઓનું મહત્વ સેટ કર્યું છે."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"શામેલ થયેલ લોકોને કારણે આ મહત્વપૂર્ણ છે."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ને <xliff:g id="ACCOUNT">%2$s</xliff:g> સાથે એક નવા વપરાશકર્તાને બનાવવાની મંજૂરી આપીએ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> સાથે <xliff:g id="APP">%1$s</xliff:g> ને એક નવા વપરાશકર્તાને બનાવવાની મંજૂરી આપીએ (આ એકાઉન્ટ સાથેના એક વપરાશકર્તા પહેલાંથી અસ્તિત્વમાં છે)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ભાષા ઉમેરો"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"પ્રદેશ પસંદગી"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ભાષાનું નામ ટાઇપ કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 32bcd4c..f21d138 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1155,7 +1155,7 @@
     <string name="whichImageCaptureApplicationNamed" msgid="8619384150737825003">"%1$s के साथ चित्र कैप्चर करें"</string>
     <string name="whichImageCaptureApplicationLabel" msgid="6390303445371527066">"चित्र कैप्चर करें"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"इस कार्रवाई के लिए डिफ़ॉल्‍ट के तौर पर इस्तेमाल करें"</string>
-    <string name="use_a_different_app" msgid="8134926230585710243">"किसी भिन्न ऐप्स का उपयोग करें"</string>
+    <string name="use_a_different_app" msgid="8134926230585710243">"किसी दूसरे ऐप्लिकेशन का इस्तेमाल करें"</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"सिस्‍टम सेटिंग और डाउनलोड किए गए ऐप में डिफ़ॉल्‍ट साफ़ करें."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"कोई कार्रवाई चुनें"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"USB डिवाइस के लिए कोई ऐप्स  चुनें"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सभी नेटवर्क देखने के लिए यहां पर टैप करें"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करें"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सभी नेटवर्क"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सुझाए गए वाई-फ़ाई नेटवर्क को अनुमति देना चाहते हैं?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> के सुझाए गए नेटवर्क. डिवाइस अपने आप कनेक्ट हो सकता है."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमति दें"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"रहने दें"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाई-फ़ाई अपने आप चालू हो जाएगा"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"जब आप किसी अच्छी क्वालिटी वाले सेव किए गए नेटवर्क के पास हों"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"वापस चालू न करें"</string>
@@ -1351,7 +1347,7 @@
     <string name="dlg_ok" msgid="7376953167039865701">"ठीक है"</string>
     <string name="usb_charging_notification_title" msgid="1595122345358177163">"यह डिवाइस यूएसबी से चार्ज हो रहा है"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"जोड़ा गया डिवाइस यूएसबी के ज़रिए चार्ज हो रहा है"</string>
-    <string name="usb_mtp_notification_title" msgid="4238227258391151029">"यूएसबी फ़ाइल ट्रांसफ़र की सुविधा चालू की गई"</string>
+    <string name="usb_mtp_notification_title" msgid="4238227258391151029">"यूएसबी फ़ाइल ट्रांसफ़र करने की सुविधा चालू की गई"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"यूएसबी के ज़रिए पीटीपी की सुविधा चालू की गई"</string>
     <string name="usb_tether_notification_title" msgid="3716143122035802501">"यूएसबी टेदरिंग की सुविधा चालू की गई"</string>
     <string name="usb_midi_notification_title" msgid="5356040379749154805">"यूएसबी के ज़रिए एमआईडीआई (मिडी) की सुविधा चालू की गई"</string>
@@ -1360,7 +1356,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"जोड़ा गया डिवाइस चार्ज हो रहा है. ज़्यादा विकल्पों के लिए टैप करें."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"एनालॉग ऑडियो एक्सेसरी का पता चला"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"अटैच किया गया डिवाइस इस फ़ोन से संगत नहीं है. ज़्यादा जानने के लिए टैप करें."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"डीबग करने के लिए एडीबी कनेक्ट किया गया"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"यूएसबी डीबग करने के लिए एडीबी कनेक्ट किया गया"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"यूएसबी को डीबग करने की सुविधा बंद करने के लिए टैप करें"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB डीबग करना अक्षम करने के लिए चुनें."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"टेस्ट हार्नेस मोड चालू किया गया"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"बिना किसी श्रेणी के"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"आपने इन सूचनाओं की अहमियत सेट की है."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"यह मौजूद व्यक्तियों के कारण महत्वपूर्ण है."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> को <xliff:g id="ACCOUNT">%2$s</xliff:g> के ज़रिये एक नया उपयोगकर्ता बनाने दें?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> को <xliff:g id="ACCOUNT">%2$s</xliff:g> के ज़रिये एक नया उपयोगकर्ता बनाने दें (इस खाते वाले एक उपयोगकर्ता पहले से मौजूद हैं)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"भाषा जोड़ें"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"क्षेत्र प्राथमिकता"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषा का नाम लिखें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 13d8433..25007d5 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dodirnite za prikaz svih mreža"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Poveži"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Sve mreže"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite li dopustiti predložene Wi‑Fi mreže?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mreže koje predlaže aplikacija <xliff:g id="NAME">%s</xliff:g>. Uređaji se mogu povezati automatski."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dopusti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi će se uključiti automatski"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kada ste u blizini spremljene mreže visoke kvalitete"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Više ne uključuj"</string>
@@ -1700,7 +1696,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Prečac pristupačnosti isključio je uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Pritisnite i zadržite tipke za glasnoću na tri sekunde da biste koristili uslugu <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Odaberite uslugu koju ćete upotrebljavati kad dodirnete gumb pristupačnosti:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Odaberite uslugu koju ćete upotrebljavati uz pokret pristupačnosti (prelazak s dva prsta prema gore od dna zaslona):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Odaberite za što će se upotrebljavati pokret pristupačnosti (prelazak s dva prsta prema gore od dna zaslona):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Odaberite uslugu koju ćete upotrebljavati uz pokret pristupačnosti (prelazak s tri prsta prema gore od dna zaslona):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Da biste prešli na neku drugu uslugu, dodirnite i zadržite gumb pristupačnosti."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Da biste prešli na neku drugu uslugu, prijeđite s dva prsta prema gore i zadržite."</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nema kategorije"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Postavili ste važnost tih obavijesti."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Važno je zbog uključenih osoba."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Želite li dopustiti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da izradi novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Želite li dopustiti aplikaciji <xliff:g id="APP">%1$s</xliff:g> da izradi novog korisnika s računom <xliff:g id="ACCOUNT">%2$s</xliff:g> (korisnik s tim računom već postoji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodavanje jezika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Postavke regije"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Unesite naziv jezika"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index a08a180..0487601a 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Koppintással megjelenítheti az összes hálózatot"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kapcsolódás"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Összes hálózat"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Engedélyezi a javasolt Wi-Fi-hálózatokat?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"A(z) <xliff:g id="NAME">%s</xliff:g> hálózatokat javasolt. Az eszköz automatikusan csatlakozhat hozzájuk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Engedélyezés"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nem, köszönöm"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"A Wi-Fi automatikusan bekapcsol"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Amikor jó minőségű mentett hálózat közelében tartózkodik"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne kapcsolódjon vissza"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nincs kategóriába sorolva"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ön állította be ezen értesítések fontossági szintjét."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ez az üzenet a résztvevők miatt fontos."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> számára, hogy új felhasználót hozzon létre a(z) <xliff:g id="ACCOUNT">%2$s</xliff:g> fiókkal?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Engedélyezi a(z) <xliff:g id="APP">%1$s</xliff:g> számára, hogy új felhasználót hozzon létre a(z) <xliff:g id="ACCOUNT">%2$s</xliff:g> fiókkal? (Már létezik felhasználó ezzel a fiókkal.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Nyelv hozzáadása"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Régió beállítása"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Adja meg a nyelvet"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 6b02ea1..e6223f0 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Հպեք՝ բոլոր ցանցերը տեսնելու համար"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Միանալ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Բոլոր ցանցերը"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Թույլատրե՞լ առաջարկվող Wi‑Fi ցանցերի օգտագործումը"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> հավելվածի առաջարկվող ցանցեր: Սարքը կարող է ավտոմատ միանալ:"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Թույլատրել"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ոչ, շնորհակալություն"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi-ն ավտոմատ կմիանա"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Լավ ազդանշանով պահված ցանցի տարածքում գտնվելիս"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Նորից չմիացնել"</string>
@@ -1360,9 +1356,9 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Միացված սարքի լիցքավորում: Հպեք՝ ավելի շատ ընտրանքների համար:"</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Հայտնաբերված է անալոգային աուդիո լրասարք"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Կցված սարքը համատեղելի չէ այս հեռախոսի հետ: Հպեք` ավելին իմանալու համար:"</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB վրիպազերծումը միացված է"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Հպեք՝ USB վրիպազերծումն անջատելու համար"</string>
-    <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Ընտրել` USB կարգաբերումը կասեցնելու համար:"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB-ով վրիպազերծումը միացված է"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Հպեք՝ USB-ով վրիպազերծումն անջատելու համար"</string>
+    <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Սեղմեք՝ USB-ով վրիպազերծումն անջատելու համար:"</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Թեստային ռեժիմը միացված է"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Թեստային ռեժիմն անջատելու համար զրոյացրեք կարգավորումները։"</string>
     <string name="usb_contaminant_detected_title" msgid="7136400633704058349">"USB միացքում ջուր կամ աղտ է հայտնաբերվել"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Չդասակարգված"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Դուք սահմանել եք այս ծանուցումների կարևորությունը:"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Կարևոր է, քանի որ որոշակի մարդիկ են ներգրավված:"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Թույլատրե՞լ <xliff:g id="APP">%1$s</xliff:g> հավելվածին <xliff:g id="ACCOUNT">%2$s</xliff:g> հաշվով նոր Օգտատեր ստեղծել:"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Թույլատրե՞լ <xliff:g id="APP">%1$s</xliff:g> հավելվածին <xliff:g id="ACCOUNT">%2$s</xliff:g> հաշվով նոր Օգտատեր ստեղծել (նման հաշվով Օգտատեր արդեն գոյություն ունի):"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ավելացնել լեզու"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Նախընտրելի տարածաշրջան"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Մուտքագրեք լեզուն"</string>
@@ -1934,7 +1932,7 @@
     <string name="app_category_maps" msgid="5878491404538024367">"Քարտեզներ և նավարկում"</string>
     <string name="app_category_productivity" msgid="3742083261781538852">"Արդյունավետություն"</string>
     <string name="device_storage_monitor_notification_channel" msgid="3295871267414816228">"Սարքի հիշողություն"</string>
-    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB վրիպազերծում"</string>
+    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB-ով վրիպազերծում"</string>
     <string name="time_picker_hour_label" msgid="2979075098868106450">"ժամ"</string>
     <string name="time_picker_minute_label" msgid="5168864173796598399">"րոպե"</string>
     <string name="time_picker_header_text" msgid="143536825321922567">"Ժամը"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index beda1c2..3c6ee83 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ketuk untuk melihat semua jaringan"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Hubungkan"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua jaringan"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Izinkan jaringan Wi-Fi yang disarankan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Jaringan yang disarankan <xliff:g id="NAME">%s</xliff:g>. Perangkat dapat terhubung secara otomatis."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Izinkan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Lain kali"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan aktif otomatis"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saat berada di dekat jaringan berkualitas tinggi yang tersimpan"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan aktifkan kembali"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Belum dikategorikan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Anda menyetel nilai penting notifikasi ini."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ini penting karena orang-orang yang terlibat."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Izinkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baru dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Izinkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baru dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> (Pengguna dengan akun ini sudah ada) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tambahkan bahasa"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferensi wilayah"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ketik nama bahasa"</string>
@@ -1983,7 +1981,7 @@
     <string name="popup_window_default_title" msgid="4874318849712115433">"Jendela Pop-up"</string>
     <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
     <string name="shortcut_restored_on_lower_version" msgid="4860853725206702336">"Versi aplikasi di-downgrade, atau tidak kompatibel dengan pintasan ini"</string>
-    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan karena aplikasi tidak mendukung backup dan pulihkan"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan karena aplikasi tidak mendukung pencadangan dan pemulihan"</string>
     <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Tidak dapat memulihkan pintasan karena tanda tangan aplikasi tidak cocok"</string>
     <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Tidak dapat memulihkan pintasan."</string>
     <string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"Pintasan dinonaktifkan"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index f4be925..59cd4c1 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ýttu til að sjá öll netkerfi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Tengjast"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Öll netkerfi"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Leyfa ráðlögð Wi‑Fi net?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> kom með tillögur að netkerfum. Tækið gæti tengst sjálfkrafa."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Leyfa"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nei, takk"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kveikt verður sjálfkrafa á Wi‑Fi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Þegar þú ert nálægt vistuðu hágæðaneti"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ekki kveikja aftur"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Óflokkað"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Þú stilltir mikilvægi þessara tilkynninga."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Þetta er mikilvægt vegna fólksins sem tekur þátt í þessu."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að stofna nýjan notanda með <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Leyfa <xliff:g id="APP">%1$s</xliff:g> að stofna nýjan notanda með <xliff:g id="ACCOUNT">%2$s</xliff:g> (notandi með þennan reikning er þegar fyrir hendi)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Bæta við tungumáli"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Svæðisval"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Sláðu inn heiti tungumáls"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 1c3d684..cbc9989 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tocca per vedere tutte le reti"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Connetti"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tutte le reti"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vuoi consentire le reti Wi-Fi suggerite?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ha suggerito delle reti. Il dispositivo potrebbe collegarsi automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Consenti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"No, grazie"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Il Wi‑Fi verrà attivato automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando ti trovi nell\'area di una rete salvata di alta qualità"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Non riattivare"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Senza categoria"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Stabilisci tu l\'importanza di queste notifiche."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Importante a causa delle persone coinvolte."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Consentire a <xliff:g id="APP">%1$s</xliff:g> di creare un nuovo utente con <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Consentire a <xliff:g id="APP">%1$s</xliff:g> di creare un nuovo utente con <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Esiste già un utente con questo account)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Aggiungi una lingua"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Area geografica preferita"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digita nome lingua"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 8ca1d23..0a721a8 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -102,7 +102,7 @@
     <string name="peerTtyModeHco" msgid="5728602160669216784">"‏העמית ביקש TTY במצב HCO"</string>
     <string name="peerTtyModeVco" msgid="1742404978686538049">"‏העמית ביקש TTY במצב VCO"</string>
     <string name="peerTtyModeOff" msgid="3280819717850602205">"‏העמית ביקש TTY במצב OFF"</string>
-    <string name="serviceClassVoice" msgid="1258393812335258019">"Google Voice"</string>
+    <string name="serviceClassVoice" msgid="1258393812335258019">"קול"</string>
     <string name="serviceClassData" msgid="872456782077937893">"Google Data"</string>
     <string name="serviceClassFAX" msgid="5566624998840486475">"פקס"</string>
     <string name="serviceClassSMS" msgid="2015460373701527489">"SMS"</string>
@@ -440,10 +440,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"האפליקציה מזהה את הפעילות הגופנית שלך."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"צלם תמונות וסרטונים"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"אפליקציה זו יכולה להשתמש במצלמה כדי לצלם תמונות ולהקליט סרטונים בכל עת."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"הרשאת גישה לאפליקציה או לשירות למצלמות המערכת כדי לצלם תמונות וסרטונים"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"‏לאפליקציית המערכת | הזו יש הרשאות מיוחדות והיא יכולה לצלם תמונות ולהקליט סרטונים באמצעות מצלמת מערכת בכל זמן. בנוסף, לאפליקציה נדרשת ההרשאה android.permission.CAMERA"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"שליטה ברטט"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"מאפשר לאפליקציה לשלוט ברטט."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"התקשר ישירות למספרי טלפון"</string>
@@ -1303,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"יש להקיש כדי לראות את כל הרשתות"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"התחבר"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"כל הרשתות"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏לאפשר הצעות לרשתות Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"הצעות לרשתות <xliff:g id="NAME">%s</xliff:g>. ייתכן שחיבור המכשיר ייעשה באופן אוטומטי."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"אישור"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"לא תודה"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏ה-Wi-Fi יופעל אוטומטית"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"כשתימצאו בקרבת רשת באיכות גבוהה ששמרתם"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"אל תפעיל שוב"</string>
@@ -1964,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ללא שיוך לקטגוריה"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"עליך להגדיר את החשיבות של ההתראות האלה."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ההודעה חשובה בשל האנשים המעורבים."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"האם לאפשר ל-<xliff:g id="APP">%1$s</xliff:g> ליצור משתמש חדש לחשבון <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"האם לאפשר ל-<xliff:g id="APP">%1$s</xliff:g> ליצור משתמש חדש לחשבון <xliff:g id="ACCOUNT">%2$s</xliff:g> (כבר קיים משתמש לחשבון הזה) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"הוספת שפה"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"העדפת אזור"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"הקלד שם שפה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 861518c..fe45c1f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"すべてのネットワークを表示するにはタップします"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"接続"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"すべてのネットワーク"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Wi‑Fi ネットワーク候補を許可しますか?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> からのネットワーク候補に、デバイスが自動的に接続される可能性があります。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"許可"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"許可しない"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi は自動的にオンになります"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"高品質の保存済みネットワークの検出時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"再度オンにしない"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"カテゴリなし"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"このような通知の重要度を設定します。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"関係するユーザーのため、この設定は重要です。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> が <xliff:g id="ACCOUNT">%2$s</xliff:g> で新しいユーザーを作成できるようにしますか?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> が <xliff:g id="ACCOUNT">%2$s</xliff:g> で新しいユーザーを作成できるようにしますか?(このアカウントのユーザーはすでに存在します)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"言語を追加"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地域設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"言語名を入力"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 1dba3ca..00afe026 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"შეეხეთ ყველა ქსელის სანახავად"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"დაკავშირება"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ყველა ქსელი"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"გსურთ, დაუშვათ შემოთავაზებული Wi‑Fi ქსელები?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> შემოთავაზებული ქსელები. მოწყობილობა შეიძლება ავტომატურად დაუკავშირდეს."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"დაშვება"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"არა, გმადლობთ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi ავტომატურად ჩაირთვება"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"როცა შენახულ მაღალხარისხიან ქსელებთან ახლოს იმყოფებით"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ხელახლა ნუ ჩართავ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"კატეგორიის გარეშე"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ამ შეტყობინებების მნიშვნელობის დონე განისაზღვრა თქვენ მიერ."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"მნიშვნელოვანია ჩართული მომხმარებლების გამო."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"მიეცეს უფლება <xliff:g id="APP">%1$s</xliff:g>-ს, <xliff:g id="ACCOUNT">%2$s</xliff:g>-ის მეშვეობით ახალი მომხმარებელი შექმნას ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"მიეცეს უფლება <xliff:g id="APP">%1$s</xliff:g>-ს, <xliff:g id="ACCOUNT">%2$s</xliff:g>-ის მეშვეობით ახალი მომხმარებელი შექმნას (ამ ანგარიშის მქონე მომხმარებელი უკვე არსებობს) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ენის დამატება"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"რეგიონის პარამეტრები"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"აკრიფეთ ენის სახელი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 504ce74..81f512b 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Барлық желілерді көру үшін түртіңіз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Қосылу"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Барлық желілер"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Ұсынылған Wi‑Fi желілеріне рұқсат беру керек пе?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ұсынған желілер. Құрылғы автоматты түрде қосылуы мүмкін."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Рұқсат беру"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Жоқ, рақмет"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматты түрде қосылады"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Сақталған жоғары сапалы желіге жақын болғанда"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Қайта қоспау"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Санатқа жатқызылмаған"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Сіз осы хабарландырулардың маңыздылығын орнатасыз."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Қатысты адамдарға байланысты бұл маңызды."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACCOUNT">%2$s</xliff:g> есептік жазбасы бар жаңа пайдаланушы жасауға рұқсат ету керек пе?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACCOUNT">%2$s</xliff:g> есептік жазбасында жаңа пайдаланушы жасауға рұқсат ету керек пе (осы есептік жазбасы бар пайдаланушы әлдеқашан бар) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Тілді қосу"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Аймақ параметрі"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Тіл атауын теріңіз"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 33aa8b2..539747b 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1259,14 +1259,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ចុចដើម្បីមើលបណ្តាញទាំងអស់"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ភ្ជាប់"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"បណ្ដាញ​ទាំងអស់"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"អនុញ្ញាតឱ្យ​ភ្ជាប់​បណ្ដាញ Wi‑Fi ដែលបាន​ណែនាំ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"បណ្ដាញ​ដែលបាន​ណែនាំ​របស់ <xliff:g id="NAME">%s</xliff:g> ។ ឧបករណ៍​អាច​ភ្ជាប់​ដោយស្វ័យប្រវត្តិ។"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"អនុញ្ញាត"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ទេ អរគុណ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi នឹង​បើក​ដោយ​ស្វ័យប្រវត្តិ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"នៅពេល​ដែល​អ្នក​នៅ​ជិត​បណ្តាញ​គុណភាព​ខ្ពស់​ដែល​បាន​រក្សាទុក"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"កុំ​បើក​ឡើង​វិញ"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"មិន​​បែងចែក​ប្រភេទ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"អ្នកបានកំណត់សារៈសំខាន់នៃការជូនដំណឹងទាំងនេះ"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"វាមានសារៈសំខាន់ដោយសារតែមនុស្សដែលពាក់ព័ន្ធ"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"អនុញ្ញាតឲ្យ <xliff:g id="APP">%1$s</xliff:g> បង្កើតអ្នកប្រើថ្មីដោយប្រើ <xliff:g id="ACCOUNT">%2$s</xliff:g> ឬទេ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"អនុញ្ញាតឲ្យ <xliff:g id="APP">%1$s</xliff:g> បង្កើតអ្នកប្រើថ្មីដោយប្រើ <xliff:g id="ACCOUNT">%2$s</xliff:g> (មានអ្នកប្រើសម្រាប់គណនីនេះរួចហើយ) ឬទេ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"បន្ថែមភាសា"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ចំណូលចិត្តតំបន់"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"វាយបញ្ចូលឈ្មោះភាសា"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 654dd6b..c7d0fea 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1155,7 +1155,7 @@
     <string name="whichImageCaptureApplicationNamed" msgid="8619384150737825003">"%1$s ಜೊತೆ ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
     <string name="whichImageCaptureApplicationLabel" msgid="6390303445371527066">"ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
     <string name="alwaysUse" msgid="4583018368000610438">"ಈ ಕ್ರಿಯೆಗೆ ಡಿಫಾಲ್ಟ್ ಆಗಿ ಬಳಸಿ."</string>
-    <string name="use_a_different_app" msgid="8134926230585710243">"ಬೇರೆಯ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಿ"</string>
+    <string name="use_a_different_app" msgid="8134926230585710243">"ಬೇರೊಂದು ಆ್ಯಪ್ ಬಳಸಿ"</string>
     <string name="clearDefaultHintMsg" msgid="3252584689512077257">"ಸಿಸ್ಟಂ ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಅಪ್ಲಿಕೇಶನ್‌ಗಳು &gt; ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾದ ಡಿಫಾಲ್ಟ್‌‌ ಅನ್ನು ತೆರವುಗೊಳಿಸಿ."</string>
     <string name="chooseActivity" msgid="7486876147751803333">"ಕ್ರಿಯೆಯನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="chooseUsbActivity" msgid="6894748416073583509">"USB ಸಾಧನಕ್ಕೆ ಅಪ್ಲಿಕೇಶನ್‌‌ವೊಂದನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ಸಂಪರ್ಕಿಸಿ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ಎಲ್ಲಾ ನೆಟ್‌ವರ್ಕ್‌ಗಳು"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ಸೂಚಿಸಿರುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ಸೂಚಿಸಿರುವ ನೆಟ್‌ವರ್ಕ್‌ಗಳು. ಸಾಧನಗಳು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಂಪರ್ಕಗೊಳ್ಳಬಹುದು."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ಅನುಮತಿಸಿ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ಬೇಡ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ವೈ‑ಫೈ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಆನ್ ಆಗುತ್ತದೆ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ನೀವು ಉಳಿಸಿದ ಅಧಿಕ ಗುಣಮಟ್ಟದ ನೆಟ್‌ವರ್ಕ್‌ ಸಮೀಪದಲ್ಲಿದ್ದಾಗ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ಮತ್ತೆ ಆನ್ ಮಾಡಲು ಹಿಂತಿರುಗಬೇಡಿ"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ವರ್ಗೀಕರಿಸದಿರುವುದು"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ನೀವು ಈ ಅಧಿಸೂಚನೆಗಳ ಪ್ರಾಮುಖ್ಯತೆಯನ್ನು ಹೊಂದಿಸಿರುವಿರಿ."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ಜನರು ತೊಡಗಿಕೊಂಡಿರುವ ಕಾರಣ ಇದು ಅತ್ಯಂತ ಪ್ರಮುಖವಾಗಿದೆ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ಮೂಲಕ ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> (ಈ ಖಾತೆಯ ಬಳಕೆದಾರರು ಈಗಾಗಲೇ ಅಸ್ತಿತ್ವದಲ್ಲಿದ್ದಾರೆ) ಮೂಲಕ ಹೊಸ ಬಳಕೆದಾರರನ್ನು ರಚಿಸಲು <xliff:g id="APP">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸುವುದೇ ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ಭಾಷೆ ಸೇರಿಸಿ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ಪ್ರದೇಶ ಪ್ರಾಶಸ್ತ್ಯ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ಭಾಷೆ ಹೆಸರನ್ನು ಟೈಪ್ ಮಾಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index bc6193f..c0dd8b1 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"모든 네트워크를 보려면 탭하세요."</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"연결"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"모든 네트워크"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"제안된 Wi‑Fi 네트워크를 허용하시겠습니까?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>에서 네트워크를 제안했습니다. 기기가 자동으로 연결될 수 있습니다."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"허용"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"허용 안함"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi가 자동으로 사용 설정됨"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"저장된 고품질 네트워크가 가까이 있는 경우"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"다시 사용 설정하지 않음"</string>
@@ -1349,7 +1345,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"권한 필요 없음"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"비용이 부과될 수 있습니다."</string>
     <string name="dlg_ok" msgid="7376953167039865701">"확인"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"이 기기를 USB로 충전"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"이 기기를 USB로 충전 중"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"USB를 통해 연결된 기기 충전"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"USB 파일 전송 사용 설정됨"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"USB를 통해 PTP 사용 설정됨"</string>
@@ -1861,8 +1857,8 @@
     <string name="zen_mode_downtime_feature_name" msgid="2626974636779860146">"다운타임"</string>
     <string name="zen_mode_default_weeknights_name" msgid="3081318299464998143">"평일 밤"</string>
     <string name="zen_mode_default_weekends_name" msgid="2786495801019345244">"주말"</string>
-    <string name="zen_mode_default_events_name" msgid="8158334939013085363">"캘린더 일정 중"</string>
-    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"수면 중"</string>
+    <string name="zen_mode_default_events_name" msgid="8158334939013085363">"캘린더 일정"</string>
+    <string name="zen_mode_default_every_night_name" msgid="3012363838882944175">"수면 시간"</string>
     <string name="muted_by" msgid="5942954724562097128">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g>(이)가 일부 소리를 음소거함"</string>
     <string name="system_error_wipe_data" msgid="6608165524785354962">"사용 중인 기기 내부에 문제가 발생했습니다. 초기화할 때까지 불안정할 수 있습니다."</string>
     <string name="system_error_manufacturer" msgid="8086872414744210668">"사용 중인 기기 내부에 문제가 발생했습니다. 자세한 내용은 제조업체에 문의하세요."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"지정된 카테고리 없음"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"이러한 알림의 중요도를 설정했습니다."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"관련된 사용자가 있으므로 중요합니다."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g>이(가) <xliff:g id="ACCOUNT">%2$s</xliff:g>(으)로 신규 사용자를 만들도록 허용하시겠습니까?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>이(가) <xliff:g id="ACCOUNT">%2$s</xliff:g>(이 계정의 사용자가 이미 있음)(으)로 신규 사용자를 만들도록 허용하시겠습니까?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"언어 추가"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"지역 환경설정"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"언어 이름 입력"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 75eb9ac..9182563 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бардык тармактарды көрүү үчүн басыңыз"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Туташуу"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Бардык тармактар"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Сунушталган Wi‑Fi тармактарына туташасызбы?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> сунуштаган тармактар. Түзмөк автоматтык түрдө туташышы мүмкүн."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Уруксат берүү"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Жок, рахмат"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматтык түрдө күйөт"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Байланыш сигналы күчтүү тармактарга жакындаганда"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Өзү кайра күйбөйт"</string>
@@ -1350,7 +1346,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"Эч уруксаттын кереги жок"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"бул үчүн акы алынышы мүмкүн"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"Жарайт"</string>
-    <string name="usb_charging_notification_title" msgid="1595122345358177163">"Бул түзмөк USB аркылуу кубатталууда"</string>
+    <string name="usb_charging_notification_title" msgid="1595122345358177163">"USB аркылуу кубатталууда"</string>
     <string name="usb_supplying_notification_title" msgid="4631045789893086181">"USB аркылуу туташкан түзмөк кубатталууда"</string>
     <string name="usb_mtp_notification_title" msgid="4238227258391151029">"USB аркылуу файл өткөрүү режими күйгүзүлдү"</string>
     <string name="usb_ptp_notification_title" msgid="5425857879922006878">"USB аркылуу PTP режими күйгүзүлдү"</string>
@@ -1362,7 +1358,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Аналогдук аудио жабдуу табылды"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Тиркелген түзмөк бул телефонго шайкеш келбейт. Көбүрөөк маалымат алуу үчүн таптап коюңуз."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Мүчүлүштүктөрдү USB аркылуу оңдоо иштетилген"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"USB аркылуу мүчүлүштүктөрдү оңдоону өчүрүү үчүн таптаңыз"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"USB арклуу мүчүлштктрдү оңдоону өчрүү үчүн басып коюңуз"</string>
     <!-- no translation found for adb_active_notification_message (8470296818270110396) -->
     <skip />
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Сыноо программасынын режими иштетилди"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Категорияларга бөлүнгөн эмес"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Бул эскертмелердин маанилүүлүгүн белгиледиңиз."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Булар сиз үчүн маанилүү адамдар."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> колдонмосу <xliff:g id="ACCOUNT">%2$s</xliff:g> аккаунту менен жаңы колдонуучу түзө берсинби?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> колдонмосуна <xliff:g id="ACCOUNT">%2$s</xliff:g> аккаунту үчүн жаңы колдонуучу түзгөнгө уруксат бересизби (мындай аккаунту бар колдонуучу мурунтан эле бар)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Тил кошуу"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Чөлкөмдүк жөндөөлөр"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Тилди киргизиңиз"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index f20b90a..0189c08 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ແຕະເພື່ອເບິ່ງເຄືອຂ່າຍທັງໝົດ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ເຊື່ອມ​ຕໍ່"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ເຄືອຂ່າຍທັງໝົດ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ອະນຸຍາດເຄືອຂ່າຍ Wi‑Fi ທີ່ແນະນຳບໍ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"ເຄືອຂ່າຍ <xliff:g id="NAME">%s</xliff:g> ທີ່ແນະນຳ. ອຸປະກອນອາດເຊື່ອມຕໍ່ເອງໂດຍອັດຕະໂນມັດ."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ອະນຸຍາດ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ບໍ່, ຂອບໃຈ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ຈະມີການເປີດໃຊ້ Wi‑Fi ອັດຕະໂນມັດ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ເມື່ອທ່ານຢູ່ໃກ້ເຄືອຂ່າຍຄຸນນະພາບສູງທີ່ບັນທຶກໄວ້"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ບໍ່ຕ້ອງເປີດໃຊ້ຄືນໃໝ່"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ບໍ່​ມີ​ໝວດ​ໝູ່"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ທ່ານຕັ້ງຄວາມສຳຄັນຂອງການແຈ້ງເຕືອນເຫຼົ່ານີ້."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ຂໍ້ຄວາມນີ້ສຳຄັນເນື່ອງຈາກບຸກຄົນທີ່ກ່ຽວຂ້ອງ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"ອະນຸຍາດໃຫ້ <xliff:g id="APP">%1$s</xliff:g> ສ້າງຜູ້ໃຊ້ໃໝ່ສຳລັບ <xliff:g id="ACCOUNT">%2$s</xliff:g> ບໍ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"ອະນຸຍາດໃຫ້ <xliff:g id="APP">%1$s</xliff:g> ສ້າງຜູ້ໃຊ້ໃໝ່ສຳລັບ <xliff:g id="ACCOUNT">%2$s</xliff:g> (ຜູ້ໃຊ້ສຳລັບບັນຊີນີ້ມີຢູ່ແລ້ວ) ບໍ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ເພີ່ມພາສາ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ການຕັ້ງຄ່າພາກພື້ນ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ພິມຊື່ພາສາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 1256e3e..0d29c84 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Palieskite, jei norite matyti visus tinklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Prisijungti"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Visi tinklai"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Leisti siūlomus „Wi‑Fi“ tinklus?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"„<xliff:g id="NAME">%s</xliff:g>“ siūlomi tinklai. Įrenginys gali prisijungti automatiškai."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Leisti"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, ačiū"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"„Wi‑Fi“ bus įjungtas automatiškai"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kai būsite netoli išsaugoto aukštos kokybės tinklo"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neįjunkite vėl"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Be kategorijos"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Galite nustatyti šių pranešimų svarbą."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tai svarbu dėl susijusių žmonių."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ kurti naują <xliff:g id="ACCOUNT">%2$s</xliff:g> naudotoją?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Leisti „<xliff:g id="APP">%1$s</xliff:g>“ kurti naują <xliff:g id="ACCOUNT">%2$s</xliff:g> naudotoją (šią paskyrą naudojantis naudotojas jau yra)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pridėkite kalbą"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiono nuostata"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Įveskite kalbos pav."</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 5ff3aaf..2547ba9 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Pieskarieties, lai skatītu visus tīklus"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Izveidot savienojumu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Visi tīkli"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vai atļaut ieteiktos Wi‑Fi tīklus?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Lietotnes <xliff:g id="NAME">%s</xliff:g> ieteiktie tīkli. Ierīcē var tikt automātiski izveidots savienojums."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Atļaut"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nē, paldies"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi tiks automātiski ieslēgts"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kad atrodaties saglabāta augstas kvalitātes tīkla tuvumā"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Neieslēgt atkārtoti"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nav kategorijas"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Jūs iestatījāt šo paziņojumu svarīguma līmeni."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Tas ir svarīgi iesaistīto personu dēļ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vai atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> izveidot jaunu lietotāju, izmantojot e-pasta adresi <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vai atļaut lietotnei <xliff:g id="APP">%1$s</xliff:g> izveidot jaunu lietotāju, izmantojot e-pasta adresi <xliff:g id="ACCOUNT">%2$s</xliff:g> (lietotājs ar šādu kontu jau pastāv)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pievienot valodu"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Reģiona preference"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ierakstiet valodas nosaukumu"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index ef027a9..9949eaa 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -243,7 +243,7 @@
     <string name="global_action_toggle_silent_mode" msgid="8219525344246810925">"Тивок режим"</string>
     <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"Звукот е исклучен"</string>
     <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"Звукот е вклучен"</string>
-    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Режим на работа во авион"</string>
+    <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Авионски режим"</string>
     <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Режимот на работа во авион е вклучен"</string>
     <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Режимот на работа во авион е исклучен"</string>
     <string name="global_action_settings" msgid="1756531602592545966">"Поставки"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Допрете за да ги видите сите мрежи"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Поврзете се"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Сите мрежи"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Да се дозволат предложените Wi‑Fi мрежи?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Предложени мрежи од <xliff:g id="NAME">%s</xliff:g>. Уредот може да се поврзе автоматски."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволи"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, фала"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ќе се вклучи автоматски"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Кога сте во близина на зачувана мрежа со висок квалитет"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вклучувај повторно"</string>
@@ -1362,7 +1358,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Откриен е аналоген аудиододаток"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Приложениот уред не е компатибилен со телефонов. Допрете за да дознаете повеќе."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Поврзано е отстранување грешки преку USB"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Допрете за да го исклучите отстранувањето грешки преку USB"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Допрете за да го исклучите"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Изберете за да се оневозможи отстранување грешки на USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Овозможен е режимот на рамка за тестирање"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Извршете фабричко ресетирање за да го оневозможите режимот на рамка за тестирање."</string>
@@ -1897,8 +1893,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризирано"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ја поставивте важноста на известувањава."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ова е важно заради луѓето кои се вклучени."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дозволувате ли <xliff:g id="APP">%1$s</xliff:g> да создаде нов корисник со <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дозволувате ли <xliff:g id="APP">%1$s</xliff:g> да создаде нов корисник со <xliff:g id="ACCOUNT">%2$s</xliff:g> (веќе постои корисник со оваа сметка)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додај јазик"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Претпочитувања за регион"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Внеси име на јазик"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index f77c223..f711158 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"നിങ്ങളുടെ ശാരീരിക പ്രവർത്തനം ഈ ആപ്പിന് തിരിച്ചറിയാനാവും."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കുക"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"ഏതുസമയത്തും ക്യാമറ ഉപയോഗിച്ചുകൊണ്ട് ചിത്രങ്ങൾ എടുക്കാനും വീഡിയോകൾ റെക്കോർഡുചെയ്യാനും ഈ ആപ്പിന് കഴിയും."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ചിത്രങ്ങളും വീഡിയോകളും എടുക്കാൻ, സിസ്‌റ്റം ക്യാമറ ആക്‌സസ് ചെയ്യുന്നതിന് ആപ്പിനെയോ സേവനത്തെയോ അനുവദിക്കുക"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"സിസ്‌റ്റം ക്യാമറ ഉപയോഗിച്ച് ഏത് സമയത്തും ചിത്രങ്ങളെടുക്കാനും വീഡിയോകൾ റെക്കോർഡ് ചെയ്യാനും ഈ വിശേഷാധികാര | സിസ്‌റ്റം ആപ്പിന് കഴിയും. ആപ്പിലും android.permission.CAMERA അനുമതി ഉണ്ടായിരിക്കണം"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"വൈബ്രേറ്റുചെയ്യൽ നിയന്ത്രിക്കുക"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"വൈബ്രേറ്റർ നിയന്ത്രിക്കുന്നതിന് അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"ഫോൺ നമ്പറുകളിലേക്ക് നേരിട്ട് വിളിക്കുക"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"എല്ലാ നെറ്റ്‌വർക്കുകളും കാണാൻ ടാപ്പുചെയ്യുക"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"കണക്റ്റുചെയ്യുക"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"എല്ലാ നെറ്റ്‌വർക്കുകളും"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"നിർദ്ദേശിച്ച വെെഫെെ നെറ്റ്‌വർക്കുകൾ അനുവദിക്കണോ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> നിർദ്ദേശിച്ച നെറ്റ്‌വർക്കുകൾ. ഉപകരണം സ്വയമേവ കണക്‌റ്റ് ചെയ്‌തേക്കാം."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"അനുവദിക്കുക"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"വേണ്ട"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"വൈഫൈ സ്വമേധയാ ഓണാകും"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"നിങ്ങൾ ഉയർന്ന നിലവാരമുള്ള സംരക്ഷിക്കപ്പെട്ട നെറ്റ്‌വർക്കിനരികിലെത്തുമ്പോൾ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"തിരികെ ഓണാക്കരുത്"</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"വർഗ്ഗീകരിച്ചിട്ടില്ലാത്ത"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ഈ അറിയിപ്പുകളുടെ പ്രാധാന്യം നിങ്ങൾ സജ്ജീകരിച്ചു."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ഉൾപ്പെട്ടിട്ടുള്ള ആളുകളെ കണക്കിലെടുക്കുമ്പോള്‍ ഇത് പ്രധാനപ്പെട്ടതാണ്‌."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> എന്ന അക്കൗണ്ട് ഉപയോഗിച്ച് പുതിയൊരു ഉപയോക്താവിനെ സൃഷ്ടിക്കാൻ <xliff:g id="APP">%1$s</xliff:g> എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> എന്ന അക്കൗണ്ട് (ഈ അക്കൗണ്ട് ഉപയോഗിച്ചുള്ള ഒരു ഉപയോക്താവ് ഇതിനകം തന്നെ നിലവിലുണ്ട്) ഉപയോഗിച്ച് പുതിയൊരു ഉപയോക്താവിനെ സൃഷ്ടിക്കാൻ <xliff:g id="APP">%1$s</xliff:g> എന്ന ആപ്പിനെ അനുവദിക്കണോ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ഒരു ഭാഷ ചേർക്കുക"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"മേഖലാ മുൻഗണന"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ഭാഷയുടെ പേര് ടൈപ്പുചെയ്യുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index c530d3e..7570028 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Бүх сүлжээг харахын тулд товшино уу"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Холбогдох"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Бүх сүлжээ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Санал болгосон Wi‑Fi сүлжээг зөвшөөрөх үү?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> сүлжээ санал болголоо. Төхөөрөмж автоматаар холбогдож магадгүй."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Зөвшөөрөх"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Үгүй, баярлалаа"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi автоматаар асна"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Таныг хадгалсан, өндөр чанартай сүлжээний ойролцоо байх үед"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Буцааж асаахгүй"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ангилаагүй"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Та эдгээр мэдэгдлийн ач холбогдлыг тогтоосон."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Оролцсон хүмүүсээс шалтгаалан энэ нь өндөр ач холбогдолтой."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g>-г <xliff:g id="ACCOUNT">%2$s</xliff:g>-р шинэ Хэрэглэгч үүсгэхийг зөвшөөрөх үү?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g>-г <xliff:g id="ACCOUNT">%2$s</xliff:g>-р шинэ хэрэглэгч үүсгэхийг зөвшөөрөх үү (ийм бүртгэлтэй хэрэглэгч аль хэдийн байна) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Хэл нэмэх"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Бүс нутгийн тохиргоо"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Улсын хэлийг бичнэ үү"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 929fedd..e22bca0 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"हे अ‍ॅप तुमच्या शारीरिक अॅक्टिव्हिटी ओळखू शकते."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"चित्रे आणि व्हिडिओ घ्या"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"हा अ‍ॅप कोणत्याही वेळी कॅमेरा वापरून चित्रेे घेऊ आणि व्ह‍िडिअो रेकॉर्ड करू शकतो."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"फोटो आणि व्हिडिओ काढण्यासाठी अॅप्लिकेशन किंवा सेवेला सिस्टम कॅमेरे अॅक्सेस करण्याची अनुमती द्या"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"हे विशेषाधिकृत आहे | सिस्टम अॅप कधीही सिस्टम कॅमेरा वापरून फोटो आणि व्हिडिओ रेकॉर्ड करू शकते. अॅपला android.permission.CAMERA परवानगी देण्याचीदेखील आवश्यकता आहे"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"व्हायब्रेट नियंत्रित करा"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"अ‍ॅप ला व्हायब्रेटर नियंत्रित करण्यासाठी अनुमती देते."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"फोन नंबरवर प्रत्यक्ष कॉल करा"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सर्व नेटवर्क पाहण्यासाठी टॅप करा"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"कनेक्ट करा"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सर्व नेटवर्क"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सुचवलेल्या वाय-फाय नेटवर्कना अनुमती द्यायची का?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> सुचवलेली नेटवर्क. डिव्हाइस आपोआप कनेक्ट होऊ शकते."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमती द्या"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"नाही, नको"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"वाय-फाय आपोआप चालू होईल"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तुम्ही जेव्हा सेव्ह केलेल्या उच्च दर्जाच्या नेटवर्कजवळ असाल तेव्हा"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"पुन्हा चालू करू नका"</string>
@@ -1679,7 +1673,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"प्रवेशयोग्यता शॉर्टकटने <xliff:g id="SERVICE_NAME">%1$s</xliff:g> बंद केली"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g> वापरण्यासाठी दोन्ही व्हॉल्युम की तीन सेकंद दाबा आणि धरून ठेवा"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"तुम्ही अ‍ॅक्सेसिबिलिटी बटण दाबल्यावर वापरण्यासाठी वैशिष्ट्य निवडा:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्या सोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने दोन बोटांनी स्वाइप करा):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्यासोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने दोन बोटांनी स्वाइप करा):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"अ‍ॅक्सेसिबिलिटी जेश्चर ज्या सोबत वापराचे आहे अशी सेवा निवडा (स्क्रीनच्या खालच्या बाजूने तीन बोटांनी स्वाइप करा):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"सेवांदरम्यान स्विच करण्यासाठी, अ‍ॅक्सेसिबिलिटी बटणाला स्पर्श करा आणि धरून ठेवा."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"सेवांदरम्यान स्विच करण्यासाठी, दोन बोटांनी वरच्या दिशेला स्वाइप करा आणि धरून ठेवा."</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"वर्गीकरण न केलेले"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"तुम्ही या सूचनांचे महत्त्व सेट केले."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"सामील असलेल्या लोकांमुळे हे महत्वाचे आहे."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सह नवीन वापरकर्ता तयार करण्याची <xliff:g id="APP">%1$s</xliff:g> ला अनुमती द्यायची?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सह नवीन वापरकर्ता तयार करण्याची (हे खाते असलेला वापरकर्ता आधीपासून विद्यमान आहे) <xliff:g id="APP">%1$s</xliff:g> ला अनुमती द्यायची?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"एक भाषा जोडा"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"प्रदेश प्राधान्य"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषा नाव टाइप करा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index ed5e1fb..35e3658 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Ketik untuk melihat semua rangkaian"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Sambung"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Semua rangkaian"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Benarkan rangkaian Wi-Fi yang dicadangkan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rangkaian yang dicadangkan oleh <xliff:g id="NAME">%s</xliff:g>. Peranti mungkin disambungkan secara automatik."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Benarkan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Tidak perlu"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi akan dihidupkan secara automatik"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Apabila anda berada berdekatan dengan rangkaian disimpan yang berkualiti tinggi"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Jangan hidupkan kembali"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Tidak dikategorikan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Anda menetapkan kepentingan pemberitahuan ini."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Mesej ini penting disebabkan orang yang terlibat."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Benarkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baharu dengan <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Benarkan <xliff:g id="APP">%1$s</xliff:g> membuat Pengguna baharu dengan <xliff:g id="ACCOUNT">%2$s</xliff:g> (Pengguna dengan akaun ini sudah wujud)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Tambahkan bahasa"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Pilihan wilayah"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Taipkan nama bahasa"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 09dc818..d69b7d1 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ကွန်ရက်အားလုံးကို ကြည့်ရန် တို့ပါ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ချိတ်ဆက်ရန်"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ကွန်ရက်အားလုံး"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"အကြံပြုထားသည့် Wi‑Fi ကွန်ရက်များ ခွင့်ပြုမလား။"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> သည် ကွန်ရက်များကို အကြံပြုထားသည်။ စက်သည် အလိုအလျောက် ချိတ်ဆက်နိုင်သည်။"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ခွင့်ပြုရန်"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"မလိုပါ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ကို အလိုအလျောက်​ ပြန်ဖွင့်ပေးလိမ့်ပါမည်"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"သိမ်းဆည်းထားသည့် အရည်အသွေးမြင့်ကွန်ရက်များအနီးသို့ ရောက်ရှိသည့်အခါ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ပြန်မဖွင့်ပါနှင့်"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"အမျိုးအစားမခွဲရသေးပါ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ဤသတိပေးချက်များ၏ အရေးပါမှုကိုသတ်မှတ်ပြီးပါပြီ။"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ပါဝင်သည့်လူများကြောင့် အရေးပါပါသည်။"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ကို <xliff:g id="ACCOUNT">%2$s</xliff:g> ဖြင့်အသုံးပြုသူအသစ်ဖန်တီးခွင့်ပြုမလား။"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> ကို <xliff:g id="ACCOUNT">%2$s</xliff:g> ဖြင့်အသုံးပြုသူအသစ် ဖန်တီးခွင့်ပြုမလား (ဤအကောင့်ဖြင့် အသုံးပြုသူ ရှိနှင့်ပြီးဖြစ်သည်)။"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ဘာသာစကားတစ်ခု ထည့်ပါ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ဒေသရွေးချယ်မှု"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ဘာသာစကားအမည် ထည့်ပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0ad63bf..a597170 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"Vil du gi &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilgang til kalenderen din?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"sende og lese SMS-meldinger"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; sende og se SMS-meldinger?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; sende og se tekstmeldinger?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"Lagring"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"åpne bilder, medieinnhold og filer på enheten din"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"Vil du gi &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilgang til bilder, medier og filer på enheten din?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trykk for å se alle nettverkene"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Koble til"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle nettverk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vil du tillate foreslåtte Wi-Fi nettverk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>-foreslåtte nettverk. Enheten kan koble til automatisk."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillat"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nei takk"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi slås på automatisk"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Når du er i nærheten av et lagret nettverk av høy kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ikke slå på igjen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Uten kategori"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du angir viktigheten for disse varslene."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dette er viktig på grunn av folkene som er involvert."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vil du la <xliff:g id="APP">%1$s</xliff:g> opprette en ny bruker med <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vil du la <xliff:g id="APP">%1$s</xliff:g> opprette en ny bruker med <xliff:g id="ACCOUNT">%2$s</xliff:g>? (Det finnes allerede en bruker med denne kontoen.)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Legg til et språk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regionsinnstilling"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Skriv inn språknavn"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index d0f6ae43..0cbaaa0 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"यो अनुप्रयोगले तपाईंको शारीरिक गतिविधिको पहिचान गर्न सक्छ।"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"तस्बिरहरू र भिडियोहरू लिनुहोस्।"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"यस अनुप्रयोगले जुनसुकै समय क्यामेराको प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ।"</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"अनुप्रयोग वा सेवालाई तस्बिर र भिडियो खिच्न प्रणालीका क्यामेराहरूमाथि पहुँच राख्न दिनुहोस्"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"यस विशेषाधिकार प्राप्त अनुप्रयोगले जुनसुकै समय प्रणालीको क्यामेरा प्रयोग गरी तस्बिर खिच्न र भिडियो रेकर्ड गर्न सक्छ। अनुप्रयोगसँग पनि android.permission.CAMERA सम्बन्धी अनुमति हुनु पर्छ"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"कम्पन नियन्त्रण गर्नुहोस्"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"अनुप्रयोगलाई भाइब्रेटर नियन्त्रण गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"फोन नम्बरहरूमा सिधै कल गर्नुहोस्"</string>
@@ -1265,14 +1263,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"सबै नेटवर्कहरू हेर्न ट्याप गर्नुहोस्"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"जडान गर्नुहोस्"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"सबै नेटवर्कहरू"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"सिफारिस गरिएका Wi‑Fi नेटवर्कहरूलाई अनुमति दिनुहोस्?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ले सिफारिस गरेका नेटवर्कहरू। यन्त्र स्वतः जडान हुन सक्छ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"अनुमति दिनुहोस्"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"पर्दैन, धन्यवाद"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi स्वतः सक्रिय हुनेछ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"तपाईं कुनै सुरक्षित गरिएको उच्च गुणस्तरीय नेटवर्कको नजिक हुनुभएको अवस्थामा"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"फेरि सक्रिय नगर्नुहोला"</string>
@@ -1902,8 +1896,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"वर्गीकरण नगरिएको"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"तपाईंले यी सूचनाहरूको महत्त्व सेट गर्नुहोस् ।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"यसमा सङ्लग्न भएका मानिसहरूको कारणले गर्दा यो महत्वपूर्ण छ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सँगै नयाँ प्रयोगकर्ता सिर्जना गर्न <xliff:g id="APP">%1$s</xliff:g> लाई अनुमति दिने हो?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> सँगै नयाँ प्रयोगकर्ता सिर्जना गर्न <xliff:g id="APP">%1$s</xliff:g> लाई अनुमति दिने (यस खाताको प्रयोगकर्ता पहिले नै अवस्थित छ) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"भाषा थप्नुहोस्"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"क्षेत्रको प्राथमिकता"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"भाषाको नाम टाइप गर्नुहोस्"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 1abad6d..e898389 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tik om alle netwerken te bekijken"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Verbinding maken"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alle netwerken"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Voorgestelde wifi-netwerken toestaan?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> heeft netwerken voorgesteld. Apparaat kan automatisch verbinding maken."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Toestaan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nee, bedankt"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wifi wordt automatisch ingeschakeld"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Wanneer je in de buurt van een opgeslagen netwerk van hoge kwaliteit bent"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Niet weer inschakelen"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Geen categorie"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Je stelt het belang van deze meldingen in."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Dit is belangrijk vanwege de betrokken mensen."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Toestaan dat <xliff:g id="APP">%1$s</xliff:g> een nieuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> maakt?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Toestaan dat <xliff:g id="APP">%1$s</xliff:g> een nieuwe gebruiker met <xliff:g id="ACCOUNT">%2$s</xliff:g> maakt (er is al een gebruiker met dit account)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Een taal toevoegen"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiovoorkeur"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Typ een taalnaam"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 425f0ac..bd6d2cc 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ସମସ୍ତ ନେଟ୍‌ୱର୍କ ଦେଖିବାକୁ ଟାପ୍‍ କରନ୍ତୁ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"କନେକ୍ଟ କରନ୍ତୁ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ସମସ୍ତ ନେଟ୍‌ୱର୍କ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ପ୍ରସ୍ତାବିତ ୱାଇ-ଫାଇ ନେଟ୍‌ୱାର୍କଗୁଡ଼ିକୁ ଅନୁମତି ଦେବେ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ପ୍ରସ୍ତାବିତ ନେଟ୍‌ୱାର୍କଗୁଡ଼ିକ। ଡିଭାଇସ୍ ହୁଏତ ସ୍ୱଚାଳିତ ଭାବେ ସଂଯୋଗ ହୋଇପାରେ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ନାହିଁ, ଥାଉ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ୱାଇ-ଫାଇ ସ୍ୱଚାଳିତ ଭାବେ ଅନ୍ ହେବ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ଆପଣ ଏକ ଉଚ୍ଚ-କ୍ୱାଲିଟୀ ବିଶିଷ୍ଟ ସେଭ୍‌ କରାଯାଇଥିବା ନେଟ୍‌ୱର୍କ ପାଖରେ ଥିବା ସମୟରେ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ପୁଣି ଅନ୍‍ କରନ୍ତୁ ନାହିଁ"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ଅବର୍ଗୀକୃତ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ଏହି ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକର ପ୍ରମୁଖତା ଆପଣ ସେଟ୍‍ କରନ୍ତି।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ସମ୍ପୃକ୍ତ ଲୋକଙ୍କ କାରଣରୁ ଏହା ଗୁରୁତ୍ୱପୂର୍ଣ୍ଣ ଅଟେ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ସହ ଏକ ନୂଆ ୟୁଜର୍‌ ତିଆରି କରିବା ପାଇଁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ସହ ଏକ ନୂଆ ୟୁଜର୍‌ ତିଆରି କରିବାକୁ <xliff:g id="APP">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ (ଏହି ଆକାଉଣ୍ଟରେ ଜଣେ ୟୁଜର୍‌ ପୂର୍ବରୁ ରହିଛନ୍ତି)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ଏକ ଭାଷା ଯୋଡ଼ନ୍ତୁ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ପସନ୍ଦର ଅଞ୍ଚଳ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ଭାଷାର ନାମ ଟାଇପ୍‍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index c3b2ca8..a9e93d4 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"ਸਾਰੇ ਨੈੱਟਵਰਕਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"ਕਨੈਕਟ ਕਰੋ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"ਸਾਰੇ ਨੈੱਟਵਰਕ"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"ਕੀ ਸੁਝਾਏ ਵਾਈ-ਫਾਈ ਨੈੱਟਵਰਕ ਵਰਤਣੇ ਹਨ?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> ਦੇ ਸੁਝਾਏ ਨੈੱਟਵਰਕ। ਸ਼ਾਇਦ ਡੀਵਾਈਸ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਕਨੈਕਟ ਹੋਵੇ।"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ਵਰਤਣ ਦਿਓ"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"ਵਾਈ‑ਫਾਈ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਚੱਲ ਪਵੇਗਾ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਰੱਖਿਅਤ ਕੀਤੇ ਉੱਚ-ਗੁਣਵੱਤਾ ਵਾਲੇ ਨੈੱਟਵਰਕ ਦੇ ਨੇੜੇ ਹੋਵੋ"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ਵਾਪਸ ਚਾਲੂ ਨਾ ਕਰੋ"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ਗੈਰ-ਸ਼੍ਰੇਣੀਕਿਰਤ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ਤੁਸੀਂ ਇਹਨਾਂ ਸੂਚਨਾਵਾਂ ਦੀ ਮਹੱਤਤਾ ਸੈੱਟ ਕੀਤੀ।"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ਇਹ ਸ਼ਾਮਲ ਲੋਕਾਂ ਦੇ ਕਾਰਨ ਮਹੱਤਵਪੂਰਨ ਹੈ।"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"ਕੀ <xliff:g id="APP">%1$s</xliff:g> ਨੂੰ <xliff:g id="ACCOUNT">%2$s</xliff:g> ਨਾਲ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਉਣ ਦੀ ਮਨਜ਼ੂਰੀ ਦੇਣੀ ਹੈ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"ਕੀ <xliff:g id="APP">%1$s</xliff:g> ਨੂੰ <xliff:g id="ACCOUNT">%2$s</xliff:g> ਨਾਲ ਇੱਕ ਨਵਾਂ ਵਰਤੋਂਕਾਰ ਬਣਾਉਣ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ (ਇਸ ਖਾਤੇ ਨਾਲ ਇੱਕ ਵਰਤੋਂਕਾਰ ਪਹਿਲਾਂ ਤੋਂ ਹੀ ਮੌਜੂਦ ਹੈ) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ਇੱਕ ਭਾਸ਼ਾ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ਖੇਤਰ ਤਰਜੀਹ"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"ਭਾਸ਼ਾ ਨਾਮ ਟਾਈਪ ਕਰੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 65058a3..537089c 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Kliknij, by zobaczyć wszystkie sieci"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Połącz"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Wszystkie sieci"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Zezwalać na sugerowane sieci Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Sugerowane sieci: <xliff:g id="NAME">%s</xliff:g>. Urządzenie może łączyć się automatycznie."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Zezwól"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nie, dziękuję"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi włączy się automatycznie"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Gdy znajdziesz się w pobliżu zapisanej sieci o mocnym sygnale"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nie włączaj ponownie"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Bez kategorii"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ustawiłeś ważność tych powiadomień."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ta wiadomość jest ważna ze względu na osoby uczestniczące w wątku."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Zezwalasz aplikacji <xliff:g id="APP">%1$s</xliff:g> na utworzenie nowego użytkownika dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Zezwalasz aplikacji <xliff:g id="APP">%1$s</xliff:g> na utworzenie nowego użytkownika dla konta <xliff:g id="ACCOUNT">%2$s</xliff:g>)? Użytkownik z tym kontem już istnieje."</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodaj język"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Ustawienie regionu"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Wpisz nazwę języka"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 5c9bed4..ba8285f 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
@@ -1482,7 +1478,7 @@
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
     <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"O app para carro está sendo usado"</string>
     <string name="car_mode_disable_notification_message" msgid="7647248420931129377">"Toque para sair do app para carro."</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"Ponto de acesso ou tethering ativo"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Tethering desativado"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Fale com seu administrador para saber detalhes"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"O atalho de acessibilidade desativou o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Toque nos dois botões de volume e os mantenha pressionados por três segundo para usar o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Escolha um serviço a ser usado quando você toca no botão Acessibilidade:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para alternar entre serviços, toque no botão de acessibilidade e mantenha-o pressionado."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para alternar entre serviços, deslize de baixo para cima na tela com dois dedos sem soltar."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para alternar entre serviços, deslize de baixo para cima na tela com três dedos sem soltar."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem classificação"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Você definiu a importância dessas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Isso é importante por causa das pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um usuário com essa conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index ba28c60..c7df3a7 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ligar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Pretende permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas por <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode estabelecer ligação automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não, obrigado"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando estiver próximo de uma rede de alta qualidade guardada."</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não reativar"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Acessório de áudio analógico detetado"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"O dispositivo ligado não é compatível com este telemóvel. Toque para saber mais."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depuração USB ligada"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toque para desativar a depuração USB."</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Toque para desativar a depuração USB"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Selecione para desativar a depuração por USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Modo de estrutura de teste ativado"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Efetue uma reposição de dados de fábrica para desativar o Modo de estrutura de teste."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem categoria"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Definiu a importância destas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"É importante devido às pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Pretende permitir que o <xliff:g id="APP">%1$s</xliff:g> crie um novo utilizador com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Pretende permitir que o <xliff:g id="APP">%1$s</xliff:g> crie um novo utilizador com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um utilizador com esta conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Intr. nome do idioma"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 5c9bed4..ba8285f 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Toque para ver todas as redes"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectar"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Todas as redes"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permitir redes Wi-Fi sugeridas?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Redes sugeridas pelo app <xliff:g id="NAME">%s</xliff:g>. O dispositivo pode se conectar automaticamente."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permitir"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Não"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"O Wi‑Fi será ativado automaticamente"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Quando você estiver perto de uma rede salva de alta qualidade"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Não ativar novamente"</string>
@@ -1482,7 +1478,7 @@
     <string name="submit" msgid="1602335572089911941">"Enviar"</string>
     <string name="car_mode_disable_notification_title" msgid="5704265646471239078">"O app para carro está sendo usado"</string>
     <string name="car_mode_disable_notification_message" msgid="7647248420931129377">"Toque para sair do app para carro."</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"Vínculo ou ponto de acesso ativo"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"Ponto de acesso ou tethering ativo"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"Tethering desativado"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"Fale com seu administrador para saber detalhes"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"O atalho de acessibilidade desativou o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Toque nos dois botões de volume e os mantenha pressionados por três segundo para usar o <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Escolha um serviço a ser usado quando você toca no botão Acessibilidade:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço a ser usado com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com dois dedos):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Escolha um serviço para usar com o gesto de acessibilidade (deslizar de baixo para cima na tela com três dedos):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Para alternar entre serviços, toque no botão de acessibilidade e mantenha-o pressionado."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Para alternar entre serviços, deslize de baixo para cima na tela com dois dedos sem soltar."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Para alternar entre serviços, deslize de baixo para cima na tela com três dedos sem soltar."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Sem classificação"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Você definiu a importância dessas notificações."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Isso é importante por causa das pessoas envolvidas."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permitir que <xliff:g id="APP">%1$s</xliff:g> crie um novo usuário com <xliff:g id="ACCOUNT">%2$s</xliff:g> (já existe um usuário com essa conta)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adicionar um idioma"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferência de região"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Digitar nome do idioma"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 4386c12..7277228 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Atingeți pentru a vedea toate rețelele"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Conectați-vă"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Toate rețelele"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Permiteți rețelele Wi-Fi sugerate?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> a sugerat rețele. Dispozitivul se poate conecta automat."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Permiteți"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nu, mulțumesc"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi se va activa automat"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Când vă aflați lângă o rețea salvată, de înaltă calitate"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Nu reactivați"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Neclasificate"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Dvs. setați importanța acestor notificări."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Notificarea este importantă având în vedere persoanele implicate."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Permiteți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Permiteți ca <xliff:g id="APP">%1$s</xliff:g> să creeze un nou utilizator folosind <xliff:g id="ACCOUNT">%2$s</xliff:g>? (există deja un utilizator cu acest cont)"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Adăugați o limbă"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regiunea preferată"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Numele limbii"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 13d75e3..e458e46 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Нажмите, чтобы увидеть список сетей"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Подключиться"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Все сети"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Подключаться к предложенным сетям Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Приложение \"<xliff:g id="NAME">%s</xliff:g>\" рекомендует сети, к которым устройство может подключаться автоматически."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Разрешить"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Нет, спасибо"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi включится автоматически"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Когда вы будете в зоне действия сохраненной сети с хорошим сигналом."</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не включать снова"</string>
@@ -1724,8 +1720,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Сервис <xliff:g id="SERVICE_NAME">%1$s</xliff:g> отключен"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Чтобы использовать сервис \"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>\", нажмите и удерживайте обе клавиши громкости в течение трех секунд."</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Выберите сервис, который будет запускаться при нажатии кнопки специальных возможностей:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберите сервис, который будет запускаться жестом для доступа к специальным возможностям (провести по экрану снизу вверх двумя пальцами):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберите сервис, который будет запускаться жестом для доступа к специальным возможностям (провести по экрану снизу вверх тремя пальцами):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Выберите сервис, который будет запускаться жестом (провести по экрану снизу вверх двумя пальцами):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Выберите сервис, который будет запускаться жестом (провести по экрану снизу вверх тремя пальцами):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Для переключения между сервисами нажмите и удерживайте кнопку специальных возможностей."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Для переключения между сервисами проведите по экрану снизу вверх двумя пальцами и задержите их."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Для переключения между сервисами проведите по экрану снизу вверх тремя пальцами и задержите их."</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Без категории"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Вы определяете важность этих уведомлений."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Важное (люди)"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" создать пользователя для аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Разрешить приложению \"<xliff:g id="APP">%1$s</xliff:g>\" создать нового пользователя для аккаунта <xliff:g id="ACCOUNT">%2$s</xliff:g> (пользователь c таким аккаунтом уже есть)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Добавьте язык"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Региональные настройки"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Введите язык"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 648f319..3e0f582 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1259,14 +1259,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"සියලු ජාල බැලීමට තට්ටු කරන්න"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"සම්බන්ධ කරන්න"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"සියලු ජාල"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"යෝජිත Wi-Fi ජාල ඉඩ දෙන්නද?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> යෝජිත ජාල. උපාංගය ස්වයංක්‍රියව සම්බන්ධ වනු ඇත."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"ඉඩ දෙන්න"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"එපා, ස්තූතියි"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ස්වයංක්‍රියව ක්‍රියාත්මක වනු ඇත"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"ඔබ උසස් තත්ත්වයේ සුරැකි ජාලයක් අවට සිටින විට"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"නැවත ක්‍රියාත්මක නොකරන්න"</string>
@@ -1896,8 +1892,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"වර්ගීකරණය නොකළ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ඔබ මෙම දැනුම්දීම්වල වැදගත්කම සකසා ඇත."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"සම්බන්ධ වූ පුද්ගලයන් නිසා මෙය වැදගත් වේ."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> හට <xliff:g id="ACCOUNT">%2$s</xliff:g> සමගින් නව පරිශීලකයෙකු සෑදීමට ඉඩ දෙන්නද?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> හට <xliff:g id="ACCOUNT">%2$s</xliff:g> සමගින් නව පරිශීලකයෙකු සෑදීමට ඉඩ දෙන්නද (මෙම ගිණුම සහිත පරිශීලකයෙකු දැනටමත් සිටී) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"භාෂාවක් එක් කරන්න"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ප්‍රදේශ මනාපය"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"භාෂා නම ටයිප් කරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 0202efd..015cd91 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Klepnutím zobrazíte všetky siete"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Pripojiť"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Všetky siete"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Chcete povoliť navrhované siete Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Siete navrhuje aplikácia <xliff:g id="NAME">%s</xliff:g>. Zariadenie sa môže pripájať automaticky."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Povoliť"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nie, ďakujem"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi sa zapne automaticky"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Keď budete v blízkosti kvalitnej uloženej siete"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Znova nezapínať"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizované"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Nastavili ste dôležitosť týchto upozornení."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Táto správa je dôležitá vzhľadom na osoby, ktorých sa to týka."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> vytvoriť nového používateľa pomocou účtu <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Povoliť aplikácii <xliff:g id="APP">%1$s</xliff:g> vytvoriť nového používateľa pomocou účtu <xliff:g id="ACCOUNT">%2$s</xliff:g> (používateľ s týmto účtom už existuje)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Pridať jazyk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferovaný región"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Zadajte názov jazyka"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 4bcdadd..54da1dc 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1063,10 +1063,10 @@
       <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> minutami</item>
     </plurals>
     <plurals name="duration_hours_relative" formatted="false" msgid="676894109982008411">
-      <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> uro</item>
-      <item quantity="two">pred <xliff:g id="COUNT_1">%d</xliff:g> urama</item>
-      <item quantity="few">pred <xliff:g id="COUNT_1">%d</xliff:g> urami</item>
-      <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> urami</item>
+      <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="two">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="few">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
+      <item quantity="other">pred <xliff:g id="COUNT_1">%d</xliff:g> h</item>
     </plurals>
     <plurals name="duration_days_relative" formatted="false" msgid="2203515825765397130">
       <item quantity="one">pred <xliff:g id="COUNT_1">%d</xliff:g> dnevom</item>
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Dotaknite se, če si želite ogledati vsa omrežja"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Vzpostavi povezavo"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Vsa omrežja"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Želite dovoliti predlagana omrežja Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> – predlagana omrežja. Naprava se lahko poveže samodejno."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Dovoli"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ne, hvala"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Povezava Wi‑Fi se bo samodejno vklopila"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Ko ste v bližini zanesljivega shranjenega omrežja"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ne vklopi znova"</string>
@@ -1405,7 +1401,7 @@
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Zaznana je analogna dodatna zvočna oprema"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Priključena naprava ni združljiva s tem telefonom. Dotaknite se za več informacij."</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Iskanje napak prek USB je povezano"</string>
-    <string name="adb_active_notification_message" msgid="7463062450474107752">"Dotaknite se, če želite izklopiti odpravljanje napak prek USB-ja"</string>
+    <string name="adb_active_notification_message" msgid="7463062450474107752">"Dotaknite se, če želite izklop. odpravlj. napak prek USB-ja"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"Izberite, če želite onemogočiti iskanje in odpravljanje napak prek vrat USB."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Način preizkusnega ogrodja je omogočen"</string>
     <string name="test_harness_mode_notification_message" msgid="1343197173054407119">"Če želite onemogočiti način preizkusnega ogrodja, ponastavite napravo na tovarniške nastavitve."</string>
@@ -1543,7 +1539,7 @@
     </plurals>
     <string name="action_mode_done" msgid="7217581640461922289">"Končano"</string>
     <string name="progress_erasing" msgid="2569962663843586562">"Brisanje skupne shrambe …"</string>
-    <string name="share" msgid="1778686618230011964">"Deli z dr."</string>
+    <string name="share" msgid="1778686618230011964">"Deli"</string>
     <string name="find" msgid="4808270900322985960">"Najdi"</string>
     <string name="websearch" msgid="4337157977400211589">"Spletno iskanje"</string>
     <string name="find_next" msgid="5742124618942193978">"Najdi naslednje"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Nekategorizirano"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Vi določite raven pomembnosti teh obvestil."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Pomembno zaradi udeleženih ljudi."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Dovolite, da aplikacija <xliff:g id="APP">%1$s</xliff:g> ustvari novega uporabnika za račun <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Dovolite aplikaciji <xliff:g id="APP">%1$s</xliff:g>, da ustvari novega uporabnika za račun <xliff:g id="ACCOUNT">%2$s</xliff:g> (uporabnik s tem računom že obstaja)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dodajanje jezika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Nastavitev območja"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Vnesite ime jezika"</string>
@@ -2057,7 +2055,7 @@
     <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Bližnjice ni bilo mogoče obnoviti zaradi neujemanja podpisa aplikacije"</string>
     <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Bližnjice ni bilo mogoče obnoviti"</string>
     <string name="shortcut_disabled_reason_unknown" msgid="5276016910284687075">"Bližnjica je onemogočena"</string>
-    <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODSTRANI"</string>
+    <string name="harmful_app_warning_uninstall" msgid="4837672735619532931">"ODMESTI"</string>
     <string name="harmful_app_warning_open_anyway" msgid="596432803680914321">"VSEENO ODPRI"</string>
     <string name="harmful_app_warning_title" msgid="8982527462829423432">"Zaznana je bila škodljiva aplikacija"</string>
     <string name="slices_permission_request" msgid="8484943441501672932">"Aplikacija <xliff:g id="APP_0">%1$s</xliff:g> želi prikazati izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index bc8d380..48ae4d7 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Trokit për të parë të gjitha rrjetet"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Lidhu"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Të gjitha rrjetet"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Të lejohen rrjetet e sugjeruara Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Rrjet e sugjeruara të <xliff:g id="NAME">%s</xliff:g>. Pajisja mund të lidhet automatikisht."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Lejo"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Jo, faleminderit"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi do të aktivizohet automatikisht"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kur ndodhesh pranë një rrjeti të ruajtur me cilësi të lartë"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Mos e aktivizo përsëri"</string>
@@ -1679,7 +1675,7 @@
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Zgjidh një shërbim për ta përdorur kur troket butonin e qasshmërisë:"</string>
     <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Zgjidh një shërbim për ta përdorur me gjestin e qasshmërisë (rrëshqit shpejt lart nga fundi i ekranit me dy gishta):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Zgjidh një shërbim për ta përdorur me gjestin e qasshmërisë (rrëshqit shpejt lart nga fundi i ekranit me tre gishta):"</string>
-    <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Për të kaluar mes shërbimeve, prek dhe mbaj prekur butonin e qasshmërisë."</string>
+    <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Për të kaluar mes shërbimeve, prek dhe mbaj të shtypur butonin e qasshmërisë."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Për të kaluar mes pajisjeve, rrëshqit shpejt lart me dy gishta dhe mbaje prekur."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Për të kaluar mes pajisjeve, rrëshqit shpejt lart me tre gishta dhe mbaje prekur."</string>
     <string name="accessibility_magnification_chooser_text" msgid="1227146738764986237">"Zmadhimi"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"E pakategorizuara"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ke caktuar rëndësinë e këtyre njoftimeve."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Është i rëndësishëm për shkak të personave të përfshirë."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Të lejohet <xliff:g id="APP">%1$s</xliff:g> që të krijojë një përdorues të ri me <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Të lejohet <xliff:g id="APP">%1$s</xliff:g> që të krijojë një përdorues të ri me <xliff:g id="ACCOUNT">%2$s</xliff:g> (një përdorues me këtë llogari ekziston tashmë) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Shto një gjuhë"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Preferenca e rajonit"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Shkruaj emrin e gjuhës"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f4f29a1..75fc1ce 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1279,14 +1279,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Додирните да бисте видели све мреже"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Повежи"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Све мреже"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Желите да дозволите предложене Wi‑Fi мреже?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Мреже које предлаже <xliff:g id="NAME">%s</xliff:g>. Уређај ће се можда повезати аутоматски."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволи"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Не, хвала"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi ће се аутоматски укључити"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Када сте у близини сачуване мреже високог квалитета"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не укључуј поново"</string>
@@ -1928,8 +1924,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Некатегоризовано"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ви подешавате важност ових обавештења."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Ово је важно због људи који учествују."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Желите ли да дозволите апликацији <xliff:g id="APP">%1$s</xliff:g> да направи новог корисника за <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Желите ли да дозволите апликацији <xliff:g id="APP">%1$s</xliff:g> да направи новог корисника за <xliff:g id="ACCOUNT">%2$s</xliff:g> (корисник са овим налогом већ постоји)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додајте језик"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Подешавање региона"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Унесите назив језика"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index ccd80ad..fdd0b7b 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tryck för att visa alla nätverk"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Anslut"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Alla nätverk"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vill du tillåta föreslagna Wi-Fi-nätverk?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Nätverk som föreslagits av <xliff:g id="NAME">%s</xliff:g>. Enheten kan anslutas automatiskt."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Tillåt"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Nej tack"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi aktiveras automatiskt"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"När du är i närheten av ett sparat nätverk av hög kvalitet"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Återaktivera inte"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Okategoriserad"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Du anger hur viktiga aviseringarna är."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Detta är viktigt på grund av personerna som deltar."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Tillåter du att <xliff:g id="APP">%1$s</xliff:g> skapar en ny användare för <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Tillåter du att <xliff:g id="APP">%1$s</xliff:g> skapar en ny användare för <xliff:g id="ACCOUNT">%2$s</xliff:g> (det finns redan en användare med det här kontot)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Lägg till ett språk"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Regionsinställningar"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Ange språket"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 4439f2c..c8c582f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Gusa ili uone mitandao yote"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Unganisha"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Mitandao yote"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Ungependa kuruhusu mitandao inayopendekezwa ya Wi-Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mitandao inayopendekezwa kwa ajili ya <xliff:g id="NAME">%s</xliff:g>. Huenda kifaa kikaunganisha kiotomatiki."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Ruhusu"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hapana, asante"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi itawashwa kiotomatiki"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Unapokuwa karibu na mtandao uliohifadhiwa wenye ubora wa juu"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Usiwashe tena"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Ambazo aina haijabainishwa"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Uliweka mipangilio ya umuhimu wa arifa hizi."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Hii ni muhimu kwa sababu ya watu waliohusika."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Ungependa kuruhusu <xliff:g id="APP">%1$s</xliff:g> iunde Mtumiaji mpya ikitumia <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Ungependa kuruhusu <xliff:g id="APP">%1$s</xliff:g> iunde Mtumiaji mpya ikitumia <xliff:g id="ACCOUNT">%2$s</xliff:g> (Je, akaunti hii tayari ina Mtumiaji)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Ongeza lugha"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Mapendeleo ya eneo"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Weka jina la lugha"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index fec5c5f..0956aad 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -341,9 +341,9 @@
     <string name="permlab_answerPhoneCalls" msgid="4077162841226223337">"ஃபோன் அழைப்புகளுக்குப் பதிலளி"</string>
     <string name="permdesc_answerPhoneCalls" msgid="2901889867993572266">"உள்வரும் ஃபோன் அழைப்பிற்குப் பதிலளிக்க, ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_receiveSms" msgid="8673471768947895082">"உரைச் செய்திகளை (SMS) பெறுதல்"</string>
-    <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிப்பதற்கு அல்லது நீக்குவதற்குப் பயன்பாட்டால் முடியும் என்பதாகும்."</string>
+    <string name="permdesc_receiveSms" msgid="6424387754228766939">"SMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிப்பதற்கு அல்லது நீக்குவதற்கு ஆப்ஸால் முடியும் என்பதாகும்."</string>
     <string name="permlab_receiveMms" msgid="1821317344668257098">"உரைச் செய்திகளை (MMS) பெறுதல்"</string>
-    <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்கவோ, நீக்கவோ பயன்பாட்டால் முடியும் என்பதாகும்."</string>
+    <string name="permdesc_receiveMms" msgid="533019437263212260">"MMS செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. இதற்கு அர்த்தம் உங்கள் சாதனத்திற்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்கவோ, நீக்கவோ ஆப்ஸால் முடியும் என்பதாகும்."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"செல் அலைபரப்புச் செய்திகளைப் படித்தல்"</string>
     <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"உங்கள் சாதனத்தில் பெறப்படும் செல் அலைபரப்புச் செய்திகளைப் படிப்பதற்குப் ஆப்ஸை அனுமதிக்கிறது. அவசரநிலை சூழ்நிலைகளை உங்களுக்கு எச்சரிக்கைச் செய்வதற்கு சில இடங்களில் செல் அலைபரப்பு விழிப்பூட்டல்கள் வழங்கப்படும். அவசரநிலை மொபைல் அலைபரப்புப் பெறப்படும்போது உங்கள் சாதனத்தின் செயல்திறன் அல்லது செயல்பாட்டுடன் தீங்கிழைக்கும் பயன்பாடுகள் அதைத் தடுக்கலாம்."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"குழுசேர்ந்த ஊட்டங்களைப் படித்தல்"</string>
@@ -356,18 +356,18 @@
     <string name="permdesc_readSms" product="default" msgid="6826832415656437652">"இந்த ஆப்ஸ் உங்கள் மொபைலில் சேமிக்கப்பட்டுள்ள எல்லா SMS (உரை) செய்திகளையும் படிக்கலாம்."</string>
     <string name="permlab_receiveWapPush" msgid="5991398711936590410">"உரைச் செய்திகளைப் (WAP) பெறுதல்"</string>
     <string name="permdesc_receiveWapPush" msgid="748232190220583385">"WAP செய்திகளைப் பெற, செயற்படுத்தப் ஆப்ஸை அனுமதிக்கிறது. உங்களுக்கு அனுப்பப்படும் செய்திகளை உங்களுக்குக் காட்டாமல் கண்காணிக்க அல்லது நீக்குவதற்கான திறன் இந்த அனுமதியில் உள்ளடங்கும்."</string>
-    <string name="permlab_getTasks" msgid="6466095396623933906">"இயங்கும் பயன்பாடுகளை மீட்டெடுத்தல்"</string>
+    <string name="permlab_getTasks" msgid="6466095396623933906">"இயங்கும் ஆப்ஸை மீட்டெடுத்தல்"</string>
     <string name="permdesc_getTasks" msgid="7454215995847658102">"நடப்பில் மற்றும் சமீபத்தில் இயங்கும் காரியங்களின் தகவலைப் பெற ஆப்ஸை அனுமதிக்கிறது. சாதனத்தில் எந்தப் பயன்பாடுகள் பயன்படுத்தப்படுகின்றன என்பது குறித்த தகவலைக் கண்டறிய ஆப்ஸை இது அனுமதிக்கலாம்."</string>
     <string name="permlab_manageProfileAndDeviceOwners" msgid="7918181259098220004">"சுயவிவரத்தையும் சாதன உரிமையாளர்களையும் நிர்வகித்தல்"</string>
-    <string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"சுயவிவர உரிமையாளர்களையும் சாதன உரிமையாளரையும் அமைக்க, பயன்பாடுகளை அனுமதிக்கிறது."</string>
-    <string name="permlab_reorderTasks" msgid="2018575526934422779">"இயங்கும் பயன்பாடுகளை மறுவரிசைப்படுத்தல்"</string>
+    <string name="permdesc_manageProfileAndDeviceOwners" msgid="106894851498657169">"சுயவிவர உரிமையாளர்களையும் சாதன உரிமையாளரையும் அமைக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
+    <string name="permlab_reorderTasks" msgid="2018575526934422779">"இயங்கும் ஆப்ஸை மறுவரிசைப்படுத்தல்"</string>
     <string name="permdesc_reorderTasks" msgid="7734217754877439351">"பின்புலத்திலும், முன்புலத்திலும் காரியங்களை நகர்த்த ஆப்ஸை அனுமதிக்கிறது. உங்கள் உள்ளீடு இல்லாமலே ஆப்ஸ் இதைச் செய்யலாம்."</string>
     <string name="permlab_enableCarMode" msgid="5684504058192921098">"கார் பயன்முறையை இயக்குதல்"</string>
     <string name="permdesc_enableCarMode" msgid="4853187425751419467">"கார் முறையை இயக்க, ஆப்ஸை அனுமதிக்கிறது."</string>
-    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"பிற பயன்பாடுகளை மூடுதல்"</string>
-    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற பயன்பாடுகளின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
-    <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே தோன்றலாம்"</string>
-    <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற பயன்பாடுகளின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
+    <string name="permlab_killBackgroundProcesses" msgid="3914026687420177202">"பிற ஆப்ஸை மூடுதல்"</string>
+    <string name="permdesc_killBackgroundProcesses" msgid="4593353235959733119">"பிற ஆப்ஸின் பின்புலச் செயல்முறைகளை நிறுத்த ஆப்ஸை அனுமதிக்கிறது. இதனால் பிற பயன்பாடுகள் இயங்குவதை நிறுத்தலாம்."</string>
+    <string name="permlab_systemAlertWindow" msgid="7238805243128138690">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே தோன்றலாம்"</string>
+    <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"இந்த ஆப்ஸ் பிற ஆப்ஸின் மேலே அல்லது திரையின் பிற பகுதிகளில் தோன்றலாம். இது வழக்கமான ஆப்ஸ் உபயோகத்தில் குறுக்கிட்டு, பிற பயன்பாடுகள் தோன்றும் விதத்தை மாற்றக்கூடும்."</string>
     <string name="permlab_runInBackground" msgid="7365290743781858803">"பின்னணியில் இயக்கு"</string>
     <string name="permdesc_runInBackground" msgid="7370142232209999824">"இந்த ஆப்ஸ், பின்னணியில் இயங்கலாம். இதனால் பேட்டரி விரைவாகத் தீர்ந்துவிடக்கூடும்."</string>
     <string name="permlab_useDataInBackground" msgid="8694951340794341809">"பின்னணியில் தரவைப் பயன்படுத்து"</string>
@@ -391,13 +391,13 @@
     <string name="permdesc_broadcastSticky" product="tv" msgid="5029460344724532288">"வலைபரப்பு முடிந்த பின்னரும் தங்கிவிடும் ஸ்டிக்கி வலைபரப்புகளை அனுப்ப ஆப்ஸை அனுமதிக்கும். அளவுக்கதிகமான உபயோகம் Android TVயின் வேகத்தைக் குறைக்கவோ நிலையற்றதாகவோ ஆக்கக்கூடும். இதனால் அதிகமான நினைவகம் பயன்படுத்தப்படும்."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"அலைபரப்பு முடிந்த பின்னும் இருக்கும், தொடர்ந்து அணுகத்தக்க அலைபரப்பை அனுப்பப் ஆப்ஸை அனுமதிக்கிறது. அதிகமாகப் பயன்படுத்தினால், மொபைலானது நினைவகத்தை மிக அதிகமாகப் பயன்படுத்துவதால் வேகம் குறைந்ததாகவும், நிலையற்றதாகவும் ஆகலாம்."</string>
     <string name="permlab_readContacts" msgid="8348481131899886131">"உங்கள் தொடர்புகளைப் படித்தல்"</string>
-    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உட்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க பயன்பாடுகளை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
+    <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உட்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
     <string name="permdesc_readContacts" product="tv" msgid="3890061004911027912">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவைத் தெரிந்துகொள்ள ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தனிநபரை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். இது உங்கள் தொடர்புத் தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கும், அத்துடன் தீங்குவிளைவிக்கும் ஆப்ஸ் உங்களுக்குத் தெரியாமல் தொடர்புத் தரவைப் பகிரக்கூடும்."</string>
-    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்ட எண்ணிக்கை உட்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க பயன்பாடுகளை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
+    <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"குறிப்பிட்டவர்களுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்ட எண்ணிக்கை உட்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைப் படிக்க ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதி, உங்கள் தொடர்பு தரவைச் சேமிக்க ஆப்ஸை அனுமதிக்கிறது, மேலும் தீங்கிழைக்கும் பயன்பாடுகள் உங்களுக்குத் தெரியாமல் தொடர்பு தரவைப் பகிரலாம்."</string>
     <string name="permlab_writeContacts" msgid="5107492086416793544">"உங்கள் தொடர்புகளை மாற்றுதல்"</string>
-    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்கப் பயன்பாடுகளை அனுமதிக்கிறது."</string>
+    <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் டேப்லெட்டில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="307929337692573341">"உங்கள் Android TVயில் சேமித்துள்ள தொடர்புகள் பற்றிய தரவை மாற்ற ஆப்ஸை அனுமதிக்கும். குறிப்பிட்ட தொடர்பை எத்தனை முறை அழைத்தீர்கள், பிறவழிகளில் தொடர்புகொண்டீர்கள் அல்லது அவருக்கு எத்தனை முறை மின்னஞ்சல் அனுப்பினீர்கள் என்பதும் இதில் அடங்கும். தொடர்புத் தரவை நீக்க ஆப்ஸை இது அனுமதிக்கும்."</string>
-    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்கப் பயன்பாடுகளை அனுமதிக்கிறது."</string>
+    <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"குறிப்பிட்ட தொடர்புகளுடன் நீங்கள் அழைத்த, மின்னஞ்சல் அனுப்பிய அல்லது வேறு வழியில் தொடர்புகொண்டதின் எண்ணிக்கை உள்பட, உங்கள் மொபைலில் சேமிக்கப்பட்ட உங்கள் தொடர்புகள் குறித்த தரவைத் திருத்த ஆப்ஸை அனுமதிக்கிறது. இந்த அனுமதியானது தொடர்புத் தரவை நீக்க ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_readCallLog" msgid="3478133184624102739">"அழைப்புப் பதிவைப் படித்தல்"</string>
     <string name="permdesc_readCallLog" msgid="3204122446463552146">"இந்த ஆப்ஸ் உங்கள் அழைப்பு வரலாற்றைப் படிக்கலாம்."</string>
     <string name="permlab_writeCallLog" msgid="8552045664743499354">"அழைப்புப் பதிவை எழுதுதல்"</string>
@@ -405,7 +405,7 @@
     <string name="permdesc_writeCallLog" product="tv" msgid="7939219462637746280">"உள்வரும், வெளிச்செல்லும் அழைப்புகள் குறித்த தகவல் உட்பட உங்கள் Android TVயின் அழைப்புப் பதிவைத் திருத்த ஆப்ஸை அனுமதிக்கும். உங்கள் அழைப்புப் பதிவை அழிக்கவோ திருத்தவோ தீங்கு விளைவிக்கும் ஆப்ஸ் இதைப் பயன்படுத்தக்கூடும்."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="683941736352787842">"உள்வரும் மற்றும் வெளிச்செல்லும் அழைப்புகள் குறித்த தகவல் உள்பட உங்கள் மொபைல் அழைப்புப் பதிவைத் திருத்துவதற்குப் ஆப்ஸை அனுமதிக்கிறது. உங்கள் அழைப்பின் பதிவை அழிக்க அல்லது திருத்த தீங்கு விளைவிக்கும் பயன்பாடுகள் இதைப் பயன்படுத்தலாம்."</string>
     <string name="permlab_bodySensors" msgid="4683341291818520277">"உடல் உணர்விகளை (இதயத் துடிப்பு மானிட்டர்கள் போன்றவை) அணுகுதல்"</string>
-    <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"உங்கள் இதயத்துடிப்பு விகிதம் போன்ற உங்கள் உடல்நிலையைக் கண்காணிக்கும் உணர்விகளில் இருந்து தரவை அணுக பயன்பாடுகளை அனுமதிக்கும்."</string>
+    <string name="permdesc_bodySensors" product="default" msgid="4380015021754180431">"உங்கள் இதயத்துடிப்பு விகிதம் போன்ற உங்கள் உடல்நிலையைக் கண்காணிக்கும் உணர்விகளில் இருந்து தரவை அணுக ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_readCalendar" msgid="6716116972752441641">"கேலெண்டர் நிகழ்வுகளையும் விவரங்களையும் படிக்கலாம்"</string>
     <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"இந்த ஆப்ஸ் உங்கள் டேப்லெட்டில் சேமிக்கப்பட்டுள்ள கேலெண்டர் நிகழ்வுகள் அனைத்தையும் படிக்கலாம், உங்கள் கேலெண்டர் தரவைப் பகிரலாம் அல்லது சேமிக்கலாம்."</string>
     <string name="permdesc_readCalendar" product="tv" msgid="1066881547471014386">"உங்கள் Android TVயில் சேமித்துள்ள அனைத்துக் கேலெண்டர் நிகழ்வுகளையும் இந்த ஆப்ஸால் தெரிந்துகொள்ள முடியும். அத்துடன் உங்களின் கேலெண்டர் தரவைப் பகிரவும் சேமிக்கவும் முடியும்."</string>
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"படங்கள் மற்றும் வீடியோக்களை எடுத்தல்"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"இந்த ஆப்ஸ் எப்போது வேண்டுமானாலும் கேமராவைப் பயன்படுத்தி படங்களை எடுக்கலாம், வீடியோக்களை ரெக்கார்டு செய்யலாம்."</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"படங்களையும் வீடியோக்களையும் எடுப்பதற்கு சிஸ்டம் கேமராக்களை அணுக ஆப்ஸையோ சேவையையோ அனுமதி"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"இந்த முன்னுரிமை பெற்ற சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். அதே போல android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"இந்த முன்னுரிமை பெற்ற சிஸ்டம் ஆப்ஸால் சிஸ்டம் கேமராவைப் பயன்படுத்தி எப்போது வேண்டுமானாலும் படங்களை எடுக்கவோ வீடியோக்களை ரெக்கார்டு செய்யவோ முடியும். android.permission.CAMERA அனுமதியும் ஆப்ஸிற்குத் தேவை"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"அதிர்வைக் கட்டுப்படுத்துதல்"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"அதிர்வைக் கட்டுப்படுத்தப் ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"தொலைபேசி எண்களை நேரடியாக அழைத்தல்"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"எல்லா நெட்வொர்க்குகளையும் பார்க்க, தட்டவும்"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"இணை"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"எல்லா நெட்வொர்க்குகளும்"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"பரிந்துரைக்கப்பட்ட வைஃபை நெட்வொர்க்குகளை அனுமதிக்க வேண்டுமா?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> பரிந்துரைக்கும் நெட்வொர்க்குகள். சாதனம் தானாக இணைக்கப்படக்கூடும்."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"அனுமதி"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"வேண்டாம்"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"வைஃபை தானாக ஆன் ஆகும்"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"சேமித்த, உயர்தர நெட்வொர்க்கிற்கு அருகில் இருக்கும்போது"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"மீண்டும் ஆன் செய்யாதே"</string>
@@ -1384,7 +1380,7 @@
     <string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
     <string name="alert_windows_notification_channel_group_name" msgid="1463953341148606396">"பிற ஆப்ஸின் மேலே காட்டு"</string>
-    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற பயன்பாடுகளின் மீது தோன்றுகிறது"</string>
+    <string name="alert_windows_notification_channel_name" msgid="3116610965549449803">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
     <string name="alert_windows_notification_title" msgid="3697657294867638947">"<xliff:g id="NAME">%s</xliff:g> பிற ஆப்ஸின் மீது தோன்றுகிறது"</string>
     <string name="alert_windows_notification_message" msgid="8917232109522912560">"<xliff:g id="NAME">%s</xliff:g> இந்த அம்சத்தைப் பயன்படுத்த வேண்டாம் என நினைத்தால், அமைப்புகளைத் திறந்து அதை முடக்க, தட்டவும்."</string>
     <string name="alert_windows_notification_turn_off_action" msgid="2902891971380544651">"ஆஃப் செய்"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"வகைப்படுத்தப்படாதவை"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"இந்த அறிவிப்புகளின் முக்கியத்துவத்தை அமைத்துள்ளீர்கள்."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ஈடுபட்டுள்ளவர்களின் காரணமாக, இது முக்கியமானது."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g> மூலம் புதிய பயனரை உருவாக்க <xliff:g id="APP">%1$s</xliff:g>ஐ அனுமதிக்கவா?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g> (இந்தக் கணக்கில் ஏற்கனவே ஒரு பயனர் உள்ளார்) மூலம் புதிய பயனரை உருவாக்க <xliff:g id="APP">%1$s</xliff:g>ஐ அனுமதிக்கவா?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"மொழியைச் சேர்"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"மண்டல விருப்பம்"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"மொழி பெயரை உள்ளிடுக"</string>
@@ -2008,7 +2006,7 @@
     <string name="standby_warning_message" product="default" msgid="5222741828239073484">"இந்தச் சாதனம் விரைவில் ஆஃப் ஆகலாம். இதைத் தொடர்ந்து ஆனில் வைக்கத் தட்டவும்."</string>
     <string name="notification_appops_camera_active" msgid="5050283058419699771">"கேமரா"</string>
     <string name="notification_appops_microphone_active" msgid="4335305527588191730">"மைக்ரோஃபோன்"</string>
-    <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற பயன்பாடுகளின் மேல் காட்டுகிறது"</string>
+    <string name="notification_appops_overlay_active" msgid="633813008357934729">"உங்கள் திரையில் உள்ள பிற ஆப்ஸின் மேல் காட்டுகிறது"</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2348803891571320452">"வழக்கமான பேட்டரி சேமிப்பானுக்கான விவர அறிவிப்பு"</string>
     <string name="dynamic_mode_notification_title" msgid="508815255807182035">"வழக்கமாகச் சார்ஜ் செய்வதற்கு முன்பே பேட்டரி தீர்ந்துபோகக்கூடும்"</string>
     <string name="dynamic_mode_notification_summary" msgid="2541166298550402690">"பேட்டரி நிலையை நீட்டிக்க பேட்டரி சேமிப்பான் இயக்கப்பட்டுள்ளது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index de1a8d6..a8550f8 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"మీ క్యాలెండర్‌ని యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ని అనుమతించాలా?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS సందేశాలను పంపడం మరియు వీక్షించడం"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"SMS సందేశాలు పంపడానికి మరియు వీక్షించడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ని అనుమతించాలా?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"SMS సందేశాలు పంపడం, చూడటం చేయగలిగేలా &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"నిల్వ"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"మీ పరికరంలోని ఫోటోలు, మీడియా మరియు ఫైల్‌లను యాక్సెస్ చేయడానికి"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"మీ పరికరంలోని ఫోటోలు, మీడియా మరియు ఫైల్‌లను యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
@@ -434,10 +434,8 @@
     <string name="permdesc_activityRecognition" msgid="3143453925156552894">"ఈ యాప్ మీ భౌతిక కార్యాకలాపాన్ని గుర్తించగలదు."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"చిత్రాలు మరియు వీడియోలు తీయడం"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"ఈ యాప్‌ కెమెరాను ఉపయోగించి ఎప్పుడైనా చిత్రాలను తీయగలదు మరియు వీడియోలను రికార్డ్ చేయగలదు."</string>
-    <!-- no translation found for permlab_systemCamera (4074081285026193898) -->
-    <skip />
-    <!-- no translation found for permdesc_systemCamera (6488131672529669229) -->
-    <skip />
+    <string name="permlab_systemCamera" msgid="4074081285026193898">"ఫోటోలు, వీడియోలు తీయడానికి సిస్టమ్ కెమెరాలకు యాప్, లేదా సేవా యాక్సెస్‌ను అనుమతించండి"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"ఈ విశేష | సిస్టమ్ యాప్ ఎప్పుడైనా సిస్టమ్ కెమెరాను ఉపయోగించి ఫోటోలు తీయగలదు, వీడియోలను రికార్డ్ చేయగలదు. యాప్‌కు android.permission.CAMERA అనుమతి ఇవ్వడం కూడా అవసరం"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"వైబ్రేషన్‌ను నియంత్రించడం"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"వైబ్రేటర్‌ను నియంత్రించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"నేరుగా కాల్ చేసే ఫోన్ నంబర్‌లు"</string>
@@ -1259,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"అన్ని నెట్‌వర్క్‌లు చూడటానికి నొక్కండి"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"కనెక్ట్ చేయి"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"అన్ని నెట్‌వర్క్‌లు"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"సూచించిన Wi‑Fi నెట్‌వర్క్‌లను అనుమతించాలా?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> సూచించిన నెట్‌వర్క్‌లు. పరికరం ఆటోమేటిక్‌గా కనెక్ట్ అవచ్చు."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"అనుమతించు"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"వద్దు"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi స్వయంచాలకంగా ఆన్ అవుతుంది"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"మీరు అధిక నాణ్యత గల సేవ్ చేసిన నెట్‌వర్క్‌కు సమీపంగా ఉన్నప్పుడు"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"తిరిగి ఆన్ చేయవద్దు"</string>
@@ -1679,7 +1673,7 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"యాక్సెస్ సామర్థ్య షార్ట్‌కట్ ద్వారా <xliff:g id="SERVICE_NAME">%1$s</xliff:g> ఆఫ్ చేయబడింది"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"<xliff:g id="SERVICE_NAME">%1$s</xliff:g>ని ఉపయోగించడానికి వాల్యూమ్ కీలు రెండింటినీ 3 సెకన్లు నొక్కి ఉంచండి"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"యాక్సెసిబిలిటీ బటన్‌ను మీరు నొక్కినప్పుడు ఉపయోగించాల్సిన ఒక ఫీచర్‌ను ఎంచుకోండి:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (రెండు చేతి వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (రెండు వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
     <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"యాక్సెసిబిలిటీ సంజ్ఞతో ఉపయోగించడానికి ఒక సేవను ఎంచుకోండి (మూడు చేతి వేళ్లతో స్క్రీన్‌ను కింద నుండి పైకి స్వైప్ చేయండి):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"సేవల మధ్య మారడానికి, యాక్సెసిబిలిటీ బటన్‌ను నొక్కి &amp; పట్టుకోండి."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"సేవల మధ్య మారడానికి, రెండు చేతి వేళ్ళతో పైకి స్వైప్ చేసి పట్టుకోండి."</string>
@@ -1897,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"వర్గీకరించబడలేదు"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"మీరు ఈ నోటిఫికేషన్‌ల ప్రాముఖ్యతను సెట్ చేసారు."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ఇందులో పేర్కొనబడిన వ్యక్తులను బట్టి ఇది చాలా ముఖ్యమైనది."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="ACCOUNT">%2$s</xliff:g>తో కొత్త వినియోగదారుని సృష్టించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించాలా ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="ACCOUNT">%2$s</xliff:g>తో (ఈ ఖాతాతో ఇప్పటికే ఒక వినియోగదారు ఉన్నారు) కొత్త వినియోగదారుని సృష్టించడానికి <xliff:g id="APP">%1$s</xliff:g>ని అనుమతించాలా?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"భాషను జోడించండి"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ప్రాంతం ప్రాధాన్యత"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"భాష పేరును టైప్ చేయండి"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index e8c776c..f2376d7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -299,7 +299,7 @@
     <string name="permgrouplab_microphone" msgid="171539900250043464">"ไมโครโฟน"</string>
     <string name="permgroupdesc_microphone" msgid="4988812113943554584">"บันทึกเสียง"</string>
     <string name="permgrouprequest_microphone" msgid="9167492350681916038">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; บันทึกเสียงไหม"</string>
-    <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"กิจกรรมการเคลื่อนไหวร่างกาย"</string>
+    <string name="permgrouplab_activityRecognition" msgid="1565108047054378642">"การเคลื่อนไหวร่างกาย"</string>
     <string name="permgroupdesc_activityRecognition" msgid="6949472038320473478">"เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณ"</string>
     <string name="permgrouprequest_activityRecognition" msgid="7626438016904799383">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึงกิจกรรมการเคลื่อนไหวร่างกายของคุณไหม"</string>
     <string name="permgrouplab_camera" msgid="4820372495894586615">"กล้องถ่ายรูป"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"แตะเพื่อดูเครือข่ายทั้งหมด"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"เชื่อมต่อ"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"เครือข่ายทั้งหมด"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"อนุญาตให้เชื่อมต่อเครือข่าย Wi-Fi ที่แนะนำไหม"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"เครือข่ายที่แนะนำโดย <xliff:g id="NAME">%s</xliff:g> และอุปกรณ์อาจเชื่อมต่อโดยอัตโนมัติ"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"อนุญาต"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"ไม่เป็นไร"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi จะเปิดโดยอัตโนมัติ"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"เมื่อคุณอยู่ใกล้เครือข่ายคุณภาพสูงที่บันทึกไว้"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"ไม่ต้องเปิดอีกครั้ง"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"ไม่จัดอยู่ในหมวดหมู่ใดๆ"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"คุณตั้งค่าความสำคัญของการแจ้งเตือนเหล่านี้"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"ข้อความนี้สำคัญเนื่องจากบุคคลที่เกี่ยวข้อง"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> สร้างผู้ใช้ใหม่ด้วย <xliff:g id="ACCOUNT">%2$s</xliff:g> ไหม"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"อนุญาตให้ <xliff:g id="APP">%1$s</xliff:g> สร้างผู้ใช้ใหม่ด้วย <xliff:g id="ACCOUNT">%2$s</xliff:g> (มีผู้ใช้ที่มีบัญชีนี้อยู่แล้ว) ไหม"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"เพิ่มภาษา"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"ค่ากำหนดภูมิภาค"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"พิมพ์ชื่อภาษา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index fc0a90c..9acd72a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"I-tap upang makita ang lahat ng network"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kumonekta"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Lahat ng network"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Payagan ang mga iminumungkahing Wi‑Fi network?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Mga iminumungkahing network ng <xliff:g id="NAME">%s</xliff:g>. Posibleng awtomatikong kumonekta ang device."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Payagan"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hindi, salamat na lang"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Awtomatikong mag-o-on ang Wi‑Fi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Kapag malapit ka sa naka-save na network na mataas ang kalidad"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Huwag i-on muli"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Di-nakategorya"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ikaw ang magtatakda sa kahalagahan ng mga notification na ito."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Mahalaga ito dahil sa mga taong kasangkot."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na gumawa ng bagong User sa <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Payagan ang <xliff:g id="APP">%1$s</xliff:g> na gumawa ng bagong User sa <xliff:g id="ACCOUNT">%2$s</xliff:g> (mayroon nang User sa account na ito) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Magdagdag ng wika"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Kagustuhan sa rehiyon"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"I-type ang wika"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 5b61b97..e2bda9f 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Tüm ağları görmek için dokunun"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Bağlan"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tüm ağlar"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Önerilen kablosuz ağlara izin verilsin mi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> tarafından önerilen ağlar. Cihaz otomatik olarak bağlanabilir."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"İzin ver"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Hayır, teşekkürler"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Kablosuz özelliği otomatik olarak açılacak"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Daha önce kaydedilmiş yüksek kaliteli bir ağın yakınında olduğunuzda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Tekrar açılmasın"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Kategorize edilmemiş"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bu bildirimlerin önem derecesini ayarladınız."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Bu, dahil olan kişiler nedeniyle önemlidir."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabına sahip yeni bir Kullanıcı eklemesine izin verilsin mi?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> uygulamasının <xliff:g id="ACCOUNT">%2$s</xliff:g> hesabına sahip yeni bir Kullanıcı eklemesine izin verilsin mi (bu hesaba sahip bir kullanıcı zaten var)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Dil ekleyin"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Bölge tercihi"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Dil adını yazın"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 0855110..ee52575 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1301,14 +1301,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Торкніться, щоб побачити всі мережі"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Під’єднатися"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Усі мережі"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Дозволити пропоновані мережі Wi‑Fi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Мережі, пропоновані додатком <xliff:g id="NAME">%s</xliff:g>. Пристрій може підключитися автоматично."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Дозволити"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Ні, дякую"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi вмикатиметься автоматично"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Коли ви поблизу збереженої мережі високої якості"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Не вмикати знову"</string>
@@ -1962,8 +1958,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Без категорії"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Ви вказуєте пріоритет цих сповіщень."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Важливе з огляду на учасників."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> створити нового користувача з обліковим записом <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Дозволити додатку <xliff:g id="APP">%1$s</xliff:g> створити нового користувача з обліковим записом <xliff:g id="ACCOUNT">%2$s</xliff:g> (користувач із таким обліковим записом уже існує)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Додати мову"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Вибір регіону"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Введіть назву мови"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index f5978e2..5cc8db7 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"تمام نیٹ ورکس دیکھنے کیلئے تھپتھپائيں"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"منسلک کریں"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"سبھی نیٹ ورکس"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"‏تجویز کردہ Wi‑Fi نیٹ ورکس کو اجازت دیں؟"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> تجویز کردہ نیٹ ورکس۔ آلہ خودکار طور پر منسلک ہو سکتا ہے۔"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"اجازت ہیں"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"نہیں شکریہ"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"‏Wi‑Fi از خود آن ہو جائے گا"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"جب آپ اعلی معیار کے محفوظ کردہ نیٹ ورک کے قریب ہوں"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"دوبارہ آن نہ کریں"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"غیر زمرہ بند"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"ان اطلاعات کی اہمیت آپ مقرر کرتے ہیں۔"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"اس میں موجود لوگوں کی وجہ سے یہ اہم ہے۔"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> کو <xliff:g id="ACCOUNT">%2$s</xliff:g> کے ساتھ ایک نیا صارف بنانے کی اجازت دیں؟"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> کو <xliff:g id="ACCOUNT">%2$s</xliff:g> کے ساتھ ایک نیا صارف بنانے کی اجازت دیں (اس اکاؤنٹ کے ساتھ ایک صارف پہلے سے موجود ہے) ؟"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"ایک زبان شامل کریں"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"علاقہ کی ترجیح"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"زبان کا نام ٹائپ کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 681e742..cb5191d 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"rasm va videoga olish"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"Bu ilova xohlagan vaqtda kamera orqali suratga olishi va video yozib olishi mumkin."</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"Ilova yoki xizmatga tizim kamerasi orqali surat va videolar olishga ruxsat berish"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Bu imtiyozli | tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA permission ruxsati ham yoqilgan boʻlishi talab qilinadi"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"Bu imtiyozli | tizim ilovasi istalgan vaqtda tizim kamerasi orqali surat va videolar olishi mumkin. Ilovada android.permission.CAMERA ruxsati ham yoqilgan boʻlishi talab qilinadi"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"tebranishni boshqarish"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Ilova tebranishli signallarni boshqarishi mumkin."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"telefon raqamlariga tog‘ridan to‘g‘ri qo‘ng‘iroq qilish"</string>
@@ -1227,12 +1227,12 @@
     <string name="volume_music_hint_silent_ringtone_selected" msgid="8310739960973156272">"Ovozsiz rejim tanlandi"</string>
     <string name="volume_call" msgid="3941680041282788711">"Suhbat vaqtidagi tovush balandligi"</string>
     <string name="volume_bluetooth_call" msgid="2002891926351151534">"Kiruvchi bluetooth tovushi"</string>
-    <string name="volume_alarm" msgid="1985191616042689100">"Signal tovushi balandligi"</string>
+    <string name="volume_alarm" msgid="1985191616042689100">"Signal tovushi"</string>
     <string name="volume_notification" msgid="2422265656744276715">"Eslatma tovushi"</string>
     <string name="volume_unknown" msgid="1400219669770445902">"Tovush balandligi"</string>
     <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Bluetooth tovushi"</string>
     <string name="volume_icon_description_ringer" msgid="3326003847006162496">"Rington balandligi"</string>
-    <string name="volume_icon_description_incall" msgid="8890073218154543397">"Qo‘ng‘iroq tovushi balandligi"</string>
+    <string name="volume_icon_description_incall" msgid="8890073218154543397">"Suhbat tovushi"</string>
     <string name="volume_icon_description_media" msgid="4217311719665194215">"Multimedia tovushi"</string>
     <string name="volume_icon_description_notification" msgid="7044986546477282274">"Eslatma tovushi"</string>
     <string name="ringtone_default" msgid="3789758980357696936">"Standart rington"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Barcha tarmoqlarni ko‘rish uchun bosing"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Ulanish"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Barcha tarmoqlar"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Tavsiya qilingan Wi‑Fi tarmoqlarga ruxsat berilsinmi?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> tavsiya qilgan tarmoqlar. Qurilma avtomatik ulanishi mumkin."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Ruxsat"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Kerak emas"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi avtomatik ravishda yoqiladi"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Saqlangan tarmoqlar ichidan signali yaxshisi hududida ekaningizda"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Qayta yoqilmasin"</string>
@@ -1361,7 +1357,7 @@
     <string name="usb_power_notification_message" msgid="4647527153291917218">"Ulangan qurilma quvvatlanmoqda. Boshqa parametrlar uchun bosing."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"Analogli audio uskuna aniqlandi"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"Biriktirilgan qurilma mazkur telefon bilan mos emas. Batafsil axborot olish uchun bu yerga bosing."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB orqali nosozliklarni aniqlash"</string>
     <string name="adb_active_notification_message" msgid="7463062450474107752">"USB orqali nosozliklarni aniqlashni faolsizlantirish uchun bosing"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB orqali nosozliklarni tuzatishni o‘chirib qo‘yish uchun bosing."</string>
     <string name="test_harness_mode_notification_title" msgid="2216359742631914387">"Xavfsizlik sinovi rejimi yoqildi"</string>
@@ -1895,8 +1891,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Turkumlanmagan"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Siz ushbu bildirishnomalarning muhimligini belgilagansiz."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Bu odamlar siz uchun muhim."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga <xliff:g id="ACCOUNT">%2$s</xliff:g> hisobi bilan yangi foydalanuvchi yaratishiga ruxsat berilsinmi ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"<xliff:g id="APP">%1$s</xliff:g> ilovasiga <xliff:g id="ACCOUNT">%2$s</xliff:g> hisobi bilan yangi foydalanuvchi yaratishiga ruxsat berilsinmi (bunday hisobdagi foydalanuvchi allaqachon mavjud) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Til qoʻshish"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Hudud sozlamalari"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Til nomini kiriting"</string>
@@ -1935,7 +1933,7 @@
     <string name="app_category_maps" msgid="5878491404538024367">"Xaritalar va navigatsiya"</string>
     <string name="app_category_productivity" msgid="3742083261781538852">"Ish va unumdorlik"</string>
     <string name="device_storage_monitor_notification_channel" msgid="3295871267414816228">"Qurilma xotirasi"</string>
-    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="adb_debugging_notification_channel_tv" msgid="5537766997350092316">"USB orqali nosozliklarni aniqlash"</string>
     <string name="time_picker_hour_label" msgid="2979075098868106450">"soat"</string>
     <string name="time_picker_minute_label" msgid="5168864173796598399">"daqiqa"</string>
     <string name="time_picker_header_text" msgid="143536825321922567">"Vaqtni sozlash"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 9f8d48a..e41dc43 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Nhấn để xem tất cả các mạng"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Kết nối"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Tất cả các mạng"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Cho phép các mạng Wi‑Fi được đề xuất?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"Các mạng do <xliff:g id="NAME">%s</xliff:g> đề xuất. Thiết bị có thể kết nối tự động."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Cho phép"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Không, cảm ơn"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi sẽ tự động bật"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Khi bạn ở gần mạng đã lưu chất lượng cao"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Không bật lại"</string>
@@ -1676,8 +1672,8 @@
     <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Đã tắt phím tắt trợ năng <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_shortcut_spoken_feedback" msgid="8376923232350078434">"Nhấn và giữ đồng thời cả hai phím âm lượng trong 3 giây để sử dụng <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_button_prompt_text" msgid="1176658502969738564">"Chọn dịch vụ sẽ sử dụng khi bạn nhấn vào nút hỗ trợ tiếp cận:"</string>
-    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận (vuốt lên từ cuối màn hình bằng 2 ngón tay):"</string>
-    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận (vuốt lên từ cuối màn hình bằng 3 ngón tay):"</string>
+    <string name="accessibility_gesture_prompt_text" msgid="8259145549733019401">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận này (vuốt lên từ cuối màn hình bằng 2 ngón tay):"</string>
+    <string name="accessibility_gesture_3finger_prompt_text" msgid="1041435574275047665">"Chọn dịch vụ sẽ sử dụng với cử chỉ hỗ trợ tiếp cận này (vuốt lên từ cuối màn hình bằng 3 ngón tay):"</string>
     <string name="accessibility_button_instructional_text" msgid="7003212763213614833">"Để chuyển đổi giữa các dịch vụ, hãy chạm và giữ nút hỗ trợ tiếp cận."</string>
     <string name="accessibility_gesture_instructional_text" msgid="5261788874937410950">"Để chuyển đổi giữa các dịch vụ, hãy vuốt lên và giữ bằng 2 ngón tay."</string>
     <string name="accessibility_gesture_3finger_instructional_text" msgid="4969448938984394550">"Để chuyển đổi giữa các dịch vụ, hãy vuốt lên và giữ bằng 3 ngón tay."</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Chưa được phân loại"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Bạn đặt tầm quan trọng của các thông báo này."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Thông báo này quan trọng vì những người có liên quan."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Cho phép <xliff:g id="APP">%1$s</xliff:g> tạo người dùng mới bằng <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Cho phép <xliff:g id="APP">%1$s</xliff:g> tạo người dùng mới bằng <xliff:g id="ACCOUNT">%2$s</xliff:g> (người dùng có tài khoản này đã tồn tại)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Thêm ngôn ngữ"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Tùy chọn khu vực"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Nhập tên ngôn ngữ"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 0293237..ba39f1e2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -283,7 +283,7 @@
     <string name="permgrouprequest_contacts" msgid="6032805601881764300">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问您的通讯录吗?"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"位置信息"</string>
     <string name="permgroupdesc_location" msgid="1346617465127855033">"获取此设备的位置信息"</string>
-    <string name="permgrouprequest_location" msgid="3788275734953323491">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”获取此设备的位置信息吗?"</string>
+    <string name="permgrouprequest_location" msgid="3788275734953323491">"要允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”获取此设备的位置信息吗?"</string>
     <string name="permgrouprequestdetail_location" msgid="1347189607421252902">"只有当您使用该应用时,该应用才有权访问位置信息"</string>
     <string name="permgroupbackgroundrequest_location" msgid="5039063878675613235">"要&lt;b&gt;一律允许&lt;/b&gt;允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问此设备的位置信息吗?"</string>
     <string name="permgroupbackgroundrequestdetail_location" msgid="4597006851453417387">"目前只有当您使用该应用时,该应用才能访问位置信息"</string>
@@ -292,7 +292,7 @@
     <string name="permgrouprequest_calendar" msgid="289900767793189421">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问您的日历吗?"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"短信"</string>
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"发送和查看短信"</string>
-    <string name="permgrouprequest_sms" msgid="7168124215838204719">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;发送和查看短信吗?"</string>
+    <string name="permgrouprequest_sms" msgid="7168124215838204719">"要允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;发送和查看短信吗?"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"存储空间"</string>
     <string name="permgroupdesc_storage" msgid="637758554581589203">"访问您设备上的照片、媒体内容和文件"</string>
     <string name="permgrouprequest_storage" msgid="7885942926944299560">"允许“<xliff:g id="APP_NAME">%1$s</xliff:g>”&lt;b&gt;&lt;/b&gt;访问您设备上的照片、媒体内容和文件吗?"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"点按即可查看所有网络"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"连接"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有网络"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"是否允许系统连接到建议的 WLAN 网络?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g>建议的网络。设备可能会自动连接到这些网络。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允许"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"WLAN 将自动开启"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"当您位于已保存的高品质网络信号范围内时"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新开启"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分类"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"这些通知的重要程度由您来设置。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"这条通知涉及特定的人,因此被归为重要通知。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"允许<xliff:g id="APP">%1$s</xliff:g>使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 创建新用户吗?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"允许<xliff:g id="APP">%1$s</xliff:g>使用 <xliff:g id="ACCOUNT">%2$s</xliff:g>(目前已有用户使用此帐号)创建新用户吗?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"添加语言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"区域偏好设置"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"输入语言名称"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 9c30e70..f0fce71 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -435,7 +435,7 @@
     <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string>
     <string name="permdesc_camera" msgid="5392231870049240670">"此應用程式可以隨時使用相機拍照和攝錄。"</string>
     <string name="permlab_systemCamera" msgid="4074081285026193898">"允許應用程式或服務存取系統相機來拍照和攝錄"</string>
-    <string name="permdesc_systemCamera" msgid="6488131672529669229">"此特別權限 | 系統應用程式可以隨時使用系統相機來拍照和攝錄。應用程式亦需要獲得 Android 權限/相機權限"</string>
+    <string name="permdesc_systemCamera" msgid="6488131672529669229">"這個獲特別權限的系統應用程式可以在任何時候使用系統相機來拍照和攝錄。此外,應用程式亦需要 android.permission.CAMERA 權限"</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"控制震動"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"允許應用程式控制震動。"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"直接撥打電話號碼"</string>
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕按即可查看所有網絡"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有網絡"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"要允許連線至建議的 Wi-Fi 網絡嗎?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"「<xliff:g id="NAME">%s</xliff:g>」已建議網絡連線,裝置可能會自動連接網絡。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允許"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了,謝謝"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi-Fi 將會自動開啟"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當您位於已儲存的高品質網絡信號範圍內時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分類"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"您可以設定這些通知的重要性。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"列為重要的原因:涉及的人。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"要允許 <xliff:g id="APP">%1$s</xliff:g> 使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"要允許 <xliff:g id="APP">%1$s</xliff:g> 使用 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者 (此帳戶目前已有此使用者) 嗎?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"新增語言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地區偏好設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"輸入語言名稱"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index ce95582..1f13208 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"輕觸即可查看所有網路"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"連線"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"所有網路"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"是否允許系統連線到建議的 Wi‑Fi 網路?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"「<xliff:g id="NAME">%s</xliff:g>」建議的網路。裝置可能會自動連線到這些網路。"</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"允許"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"不用了,謝謝"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"Wi‑Fi 將自動開啟"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"當你位於已儲存的高品質網路範圍內時"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"不要重新開啟"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"未分類"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"這些通知的重要性由你決定。"</string>
     <string name="importance_from_person" msgid="9160133597262938296">"這則通知涉及特定人士,因此被歸為重要通知。"</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"要允許 <xliff:g id="APP">%1$s</xliff:g> 為 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"要允許 <xliff:g id="APP">%1$s</xliff:g> 為 <xliff:g id="ACCOUNT">%2$s</xliff:g> 建立新使用者嗎 (這個帳戶目前已有使用者)?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"新增語言"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"地區偏好設定"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"請輸入語言名稱"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index e5e5311..f707fcc 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1257,14 +1257,10 @@
     <string name="wifi_available_content_failed_to_connect" msgid="3377406637062802645">"Thepha ukuze ubone onke amanethiwekhi"</string>
     <string name="wifi_available_action_connect" msgid="2635699628459488788">"Xhuma"</string>
     <string name="wifi_available_action_all_networks" msgid="4368435796357931006">"Onke amanethiwekhi"</string>
-    <!-- no translation found for wifi_suggestion_title (6396033039578436801) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_content (5603992011371520746) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_allow_app (7978995387498669901) -->
-    <skip />
-    <!-- no translation found for wifi_suggestion_action_disallow_app (6434097275967940372) -->
-    <skip />
+    <string name="wifi_suggestion_title" msgid="6396033039578436801">"Vumela amanethiwekhi e-Wi-Fi aphakanyisiwe?"</string>
+    <string name="wifi_suggestion_content" msgid="5603992011371520746">"<xliff:g id="NAME">%s</xliff:g> amanethiwekhi aphakanyisiwe. Idivayisi ingaxhumeka ngokuzenzakalela."</string>
+    <string name="wifi_suggestion_action_allow_app" msgid="7978995387498669901">"Vumela"</string>
+    <string name="wifi_suggestion_action_disallow_app" msgid="6434097275967940372">"Cha ngiyabonga"</string>
     <string name="wifi_wakeup_onboarding_title" msgid="228772560195634292">"I-Wi-Fi izovuleka ngokuzenzakalela"</string>
     <string name="wifi_wakeup_onboarding_subtext" msgid="3989697580301186973">"Uma useduze kwenethiwekhi yekhwalithi ephezulu elondoloziwe"</string>
     <string name="wifi_wakeup_onboarding_action_disable" msgid="838648204200836028">"Ungaphindi uvule"</string>
@@ -1894,8 +1890,10 @@
     <string name="default_notification_channel_label" msgid="5929663562028088222">"Akufakwanga esigabeni"</string>
     <string name="importance_from_user" msgid="7318955817386549931">"Usethe ukubaluleka kwalezi zaziso."</string>
     <string name="importance_from_person" msgid="9160133597262938296">"Lokhu kubalulekile ngenxa yabantu ababandakanyekayo."</string>
-    <string name="user_creation_account_exists" msgid="1942606193570143289">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukudala umsebenzisi omusha nge-<xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
-    <string name="user_creation_adding" msgid="4482658054622099197">"Vumela i-<xliff:g id="APP">%1$s</xliff:g> ukudala umsebenzisi omusha nge-<xliff:g id="ACCOUNT">%2$s</xliff:g> (umsebenzisi onale akhawunti usuvel ukhona) ?"</string>
+    <!-- no translation found for user_creation_account_exists (6559477114648176531) -->
+    <skip />
+    <!-- no translation found for user_creation_adding (9089159170398841763) -->
+    <skip />
     <string name="language_selection_title" msgid="2680677278159281088">"Engeza ulwimi"</string>
     <string name="country_selection_title" msgid="2954859441620215513">"Okuncamelayo kwesifunda"</string>
     <string name="search_language_hint" msgid="7042102592055108574">"Thayipha igama lolimi"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2cd39cb..5fd53de 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4202,6 +4202,10 @@
         -->
     </integer-array>
 
+    <!-- Default refresh rate in the zone defined by brightness and ambient thresholds.
+         If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+    <integer name="config_defaultRefreshRateInZone">0</integer>
+
     <!-- The type of the light sensor to be used by the display framework for things like
          auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
     <string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 18d7706..d2989d9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5015,9 +5015,9 @@
     <string name="importance_from_person">This is important because of the people involved.</string>
 
     <!-- Message to user that app trying to create user for an account that already exists. [CHAR LIMIT=none] -->
-    <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string>
+    <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> (a User with this account already exists) ?</string>
     <!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] -->
-    <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar">%2$s</xliff:g> (a User with this account already exists) ?</string>
+    <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string>
 
     <!-- Locale picker strings -->
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eaebd91..761e02f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -206,6 +206,8 @@
   <java-symbol type="id" name="action3" />
   <java-symbol type="id" name="action4" />
   <java-symbol type="id" name="media_seamless" />
+  <java-symbol type="id" name="media_seamless_image" />
+  <java-symbol type="id" name="media_seamless_text" />
   <java-symbol type="id" name="notification_media_seekbar_container" />
   <java-symbol type="id" name="notification_media_content" />
   <java-symbol type="id" name="notification_media_progress" />
@@ -3801,6 +3803,7 @@
 
   <!-- For high refresh rate displays -->
   <java-symbol type="integer" name="config_defaultPeakRefreshRate" />
+  <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
   <java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
   <java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
 
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index a92c500..f71c8b0 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -70,7 +70,7 @@
     <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
 
     <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
-    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075" />
+    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" />
 
     <!-- Chile: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="cl" pattern="\\d{4,5}" free="9963|9240" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 1670d49..a4c504b 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -43,7 +43,6 @@
 
     libs: [
         "android.test.runner",
-        "telephony-common",
         "testables",
         "org.apache.http.legacy",
         "android.test.base",
diff --git a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
index 1633e1a..6ec3dc9 100644
--- a/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageStatsTest.java
@@ -34,7 +34,10 @@
 
 import android.app.usage.UsageEvents.Event;
 import android.os.Parcel;
+import android.os.UserHandle;
+import android.support.test.uiautomator.UiDevice;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -121,6 +124,15 @@
     }
 
     @Test
+    public void testDataIsInCe() throws Exception {
+        final int userId = UserHandle.myUserId();
+        final String expectedPath = "/data/system_ce/" + userId + "/usagestats";
+        final String actualPath = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                .executeShellCommand("dumpsys usagestats stats-directory " + userId).trim();
+        assertEquals(expectedPath, actualPath);
+    }
+
+    @Test
     public void testParcelable() {
         left.mPackageName = "com.test";
         left.mBeginTimeStamp = 100000;
diff --git a/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java b/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
new file mode 100644
index 0000000..8ab9ddb
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/AndroidTelephonyCommonUpdaterTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.PackageBuilder.builder;
+import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_BASE;
+import static android.content.pm.SharedLibraryNames.ANDROID_HIDL_MANAGER;
+import static android.content.pm.SharedLibraryNames.ANDROID_TELEPHONY_COMMON;
+
+import android.os.Build;
+import androidx.test.filters.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link AndroidHidlUpdater}
+ */
+@SmallTest
+@RunWith(JUnit4.class)
+public class AndroidTelephonyCommonUpdaterTest extends PackageSharedLibraryUpdaterTest {
+
+    private static final String OTHER_LIBRARY = "other.library";
+    private static final String PHONE_UID = "android.uid.phone";
+
+    @Test
+    public void targeted_at_Q() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q);
+
+        PackageBuilder after = builder().targetSdkVersion(Build.VERSION_CODES.Q)
+            .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // Should add telephony-common libraries
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_phoneUID() {
+        PackageBuilder before = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q);
+
+        // Should add telephony-common libraries
+        PackageBuilder after = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_not_empty_usesLibraries() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(OTHER_LIBRARY);
+
+        // no change
+        checkBackwardsCompatibility(before, before);
+    }
+
+    @Test
+    public void targeted_at_Q_not_empty_usesLibraries_phoneUID() {
+        PackageBuilder before = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(OTHER_LIBRARY);
+
+        // The telephony-common jars should be added at the start of the list because it
+        // is not on the bootclasspath and the package targets pre-R.
+        PackageBuilder after = builder().setSharedUid(PHONE_UID)
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON, OTHER_LIBRARY);
+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_R_in_usesLibraries() {
+        PackageBuilder before = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q + 1)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        PackageBuilder after = builder()
+                .targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // Libraries are removed because they are not available for apps target >= R and not run
+        // on phone-uid
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_Q_in_usesLibraries() {
+        PackageBuilder before = builder().asSystemApp()
+                .targetSdkVersion(Build.VERSION_CODES.Q)
+                .requiredLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // No change is required because the package explicitly requests the telephony libraries
+        // and is targeted at the current version so does not need backwards compatibility.
+        checkBackwardsCompatibility(before, before);
+    }
+
+
+    @Test
+    public void targeted_at_R_in_usesOptionalLibraries() {
+        PackageBuilder before = builder().targetSdkVersion(Build.VERSION_CODES.Q + 1)
+            .optionalLibraries(ANDROID_TELEPHONY_COMMON);
+
+        // Dependency is removed, it is not available.
+        PackageBuilder after = builder().targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // Libraries are removed because they are not available for apps targeting Q+
+        checkBackwardsCompatibility(before, after);
+    }
+
+    @Test
+    public void targeted_at_R() {
+        PackageBuilder before = builder()
+            .targetSdkVersion(Build.VERSION_CODES.Q + 1);
+
+        // no change
+        checkBackwardsCompatibility(before, before);
+    }
+
+    private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) {
+        checkBackwardsCompatibility(before, after, AndroidTelephonyCommonUpdater::new);
+    }
+}
diff --git a/core/tests/coretests/src/android/content/pm/PackageBuilder.java b/core/tests/coretests/src/android/content/pm/PackageBuilder.java
index f7544af..f3a56e2 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBuilder.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBuilder.java
@@ -37,6 +37,8 @@
 
     private ArrayList<String> mOptionalLibraries;
 
+    private String mSharedUid;
+
     public static PackageBuilder builder() {
         return new PackageBuilder();
     }
@@ -47,6 +49,7 @@
         pkg.applicationInfo.flags = mFlags;
         pkg.usesLibraries = mRequiredLibraries;
         pkg.usesOptionalLibraries = mOptionalLibraries;
+        pkg.mSharedUserId = mSharedUid;
         return pkg;
     }
 
@@ -55,6 +58,11 @@
         return this;
     }
 
+    PackageBuilder setSharedUid(String uid) {
+        this.mSharedUid = uid;
+        return this;
+    }
+
     PackageBuilder asSystemApp() {
         this.mFlags |= ApplicationInfo.FLAG_SYSTEM;
         return this;
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 2fc3e36..4a93f42 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -16,16 +16,29 @@
 
 package android.content.res;
 
+import android.content.Context;
+import android.os.LocaleList;
 import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.usage.IntervalStatsProto;
+
 import junit.framework.TestCase;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Locale;
+
 /**
  * Build/install/run: bit FrameworksCoreTests:android.content.res.ConfigurationTest
  */
@@ -54,4 +67,70 @@
         config2.updateFrom(config);
         assertEquals(config2.screenLayout, Configuration.SCREENLAYOUT_COMPAT_NEEDED);
     }
+
+    @Test
+    public void testReadWriteProto() throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final File testDir = new File(context.getFilesDir(), "ConfigurationTest");
+        testDir.mkdirs();
+        final File proto = new File(testDir, "configs");
+        if (proto.exists()) {
+            proto.delete();
+        }
+
+        final Locale arabic = new Locale.Builder().setLocale(new Locale("ar", "AE")).build();
+        final Locale urdu = new Locale.Builder().setLocale(new Locale("ur", "IN")).build();
+        final Locale urduExtension = new Locale.Builder().setLocale(new Locale("ur", "IN"))
+                .setExtension('u', "nu-latn").build();
+        Configuration write = new Configuration();
+        write.setLocales(new LocaleList(arabic, urdu, urduExtension));
+        writeToProto(proto, write);
+        assertTrue("Failed to write configs to proto.", proto.exists());
+
+        final Configuration read = new Configuration();
+        try {
+            readFromProto(proto, read);
+        } finally {
+            proto.delete();
+        }
+
+        assertEquals("Missing locales in proto file written to disk.",
+                read.getLocales().size(), write.getLocales().size());
+        assertTrue("Arabic locale not found in Configuration locale list.",
+                read.getLocales().indexOf(arabic) != -1);
+        assertTrue("Urdu locale not found in Configuration locale list.",
+                read.getLocales().indexOf(urdu) != -1);
+        assertTrue("Urdu locale with extensions not found in Configuration locale list.",
+                read.getLocales().indexOf(urduExtension) != -1);
+    }
+
+    private void writeToProto(File f, Configuration config) throws Exception {
+        final AtomicFile af = new AtomicFile(f);
+        FileOutputStream fos = af.startWrite();
+        try {
+            final ProtoOutputStream protoOut = new ProtoOutputStream(fos);
+            final long token = protoOut.start(IntervalStatsProto.CONFIGURATIONS);
+            config.writeToProto(protoOut, IntervalStatsProto.Configuration.CONFIG, false, false);
+            protoOut.end(token);
+            protoOut.flush();
+            af.finishWrite(fos);
+            fos = null;
+        } finally {
+            af.failWrite(fos);
+        }
+    }
+
+    private void readFromProto(File f, Configuration config) throws Exception {
+        final AtomicFile afRead = new AtomicFile(f);
+        try (FileInputStream in = afRead.openRead()) {
+            final ProtoInputStream protoIn = new ProtoInputStream(in);
+            if (protoIn.nextField(IntervalStatsProto.CONFIGURATIONS)) {
+                final long token = protoIn.start(IntervalStatsProto.CONFIGURATIONS);
+                if (protoIn.nextField(IntervalStatsProto.Configuration.CONFIG)) {
+                    config.readFromProto(protoIn, IntervalStatsProto.Configuration.CONFIG);
+                    protoIn.end(token);
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index 9de4501..ae4edb9 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -17,13 +17,12 @@
 
 package android.os;
 
-import androidx.test.filters.MediumTest;
-
 import junit.framework.TestCase;
 
 public class ProcessTest extends TestCase {
 
-    @MediumTest
+    private static final int BAD_PID = 0;
+
     public void testProcessGetUidFromName() throws Exception {
         assertEquals(android.os.Process.SYSTEM_UID, Process.getUidForName("system"));
         assertEquals(Process.BLUETOOTH_UID, Process.getUidForName("bluetooth"));
@@ -37,7 +36,6 @@
                 Process.getUidForName("u3_a100"));
     }
 
-    @MediumTest
     public void testProcessGetUidFromNameFailure() throws Exception {
         // Failure cases
         assertEquals(-1, Process.getUidForName("u2a_foo"));
@@ -49,4 +47,29 @@
         assertEquals(-1, Process.getUidForName("u2jhsajhfkjhsafkhskafhkashfkjashfkjhaskjfdhakj3"));
     }
 
+    /**
+     * Tests getUidForPid() by ensuring that it returns the correct value when the process queried
+     * doesn't exist.
+     */
+    public void testGetUidForPidInvalidPid() {
+        assertEquals(-1, Process.getUidForPid(BAD_PID));
+    }
+
+    /**
+     * Tests getParentPid() by ensuring that it returns the correct value when the process queried
+     * doesn't exist.
+     */
+    public void testGetParentPidInvalidPid() {
+        assertEquals(-1, Process.getParentPid(BAD_PID));
+    }
+
+    /**
+     * Tests getThreadGroupLeader() by ensuring that it returns the correct value when the
+     * thread queried doesn't exist.
+     */
+    public void testGetThreadGroupLeaderInvalidTid() {
+        // This function takes a TID instead of a PID but BAD_PID should also be a bad TID.
+        assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
+    }
+
 }
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 6161108..a5a98a9 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -19,6 +19,8 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -181,6 +183,22 @@
                 logMaker.getTaggedData(MetricsEvent.FIELD_NOTIFICATION_STYLE));
     }
 
+    @Test
+    public void testIsAppGroup() {
+        StatusBarNotification sbn = getNotification(PKG, GROUP_ID_1, CHANNEL_ID);
+        assertTrue(sbn.isAppGroup());
+
+        sbn = getNotification(PKG, null, CHANNEL_ID);
+        assertFalse(sbn.isAppGroup());
+
+        Notification.Builder nb = getNotificationBuilder(null, CHANNEL_ID)
+                .setSortKey("something");
+
+        sbn = getNotification(PKG, nb);
+        assertTrue(sbn.isAppGroup());
+
+    }
+
     private StatusBarNotification getNotification(String pkg, String group, String channelId) {
         return getNotification(pkg, getNotificationBuilder(group, channelId));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index bdd3038..be2c4e96 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -86,7 +86,7 @@
     }
 
     @AfterClass
-    public static void tearDownOnce() throws Exception {
+    public static void tearDownOnce() {
         sInsetsModeSession.close();
     }
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 1a22a70..6bce651 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.View;
 
 import androidx.test.filters.LargeTest;
@@ -51,12 +53,17 @@
 public class AccessibilityCacheTest {
     private static final int WINDOW_ID_1 = 0xBEEF;
     private static final int WINDOW_ID_2 = 0xFACE;
+    private static final int WINDOW_ID_3 = 0xABCD;
+    private static final int WINDOW_ID_4 = 0xDCBA;
     private static final int SINGLE_VIEW_ID = 0xCAFE;
     private static final int OTHER_VIEW_ID = 0xCAB2;
     private static final int PARENT_VIEW_ID = 0xFED4;
     private static final int CHILD_VIEW_ID = 0xFEED;
     private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
     private static final int MOCK_CONNECTION_ID = 1;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+    private static final int DEFAULT_WINDOW_LAYER = 0;
+    private static final int SPECIFIC_WINDOW_LAYER = 5;
 
     AccessibilityCache mAccessibilityCache;
     AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
@@ -70,7 +77,7 @@
 
     @After
     public void tearDown() {
-        // Make sure we're recycling all of our window and node infos
+        // Make sure we're recycling all of our window and node infos.
         mAccessibilityCache.clear();
         AccessibilityInteractionClient.getInstance().clearCache();
     }
@@ -78,7 +85,7 @@
     @Test
     public void testEmptyCache_returnsNull() {
         assertNull(mAccessibilityCache.getNode(0, 0));
-        assertNull(mAccessibilityCache.getWindows());
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
         assertNull(mAccessibilityCache.getWindow(0));
     }
 
@@ -114,10 +121,11 @@
         try {
             windowInfo = AccessibilityWindowInfo.obtain();
             windowInfo.setId(WINDOW_ID_1);
+            windowInfo.setDisplayId(Display.DEFAULT_DISPLAY);
             mAccessibilityCache.addWindow(windowInfo);
             // Make a copy
             copyOfInfo = AccessibilityWindowInfo.obtain(windowInfo);
-            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info
+            windowInfo.setId(WINDOW_ID_2); // Simulate recycling and reusing the original info.
             windowFromCache = mAccessibilityCache.getWindow(WINDOW_ID_1);
             assertEquals(copyOfInfo, windowFromCache);
         } finally {
@@ -129,39 +137,40 @@
 
     @Test
     public void addWindowThenClear_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.clear();
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
     }
 
     @Test
     public void addWindowGetOtherId_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1 + 1));
     }
 
     @Test
     public void addWindowThenGetWindows_returnsNull() {
-        putWindowWithIdInCache(WINDOW_ID_1);
-        assertNull(mAccessibilityCache.getWindows());
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
+        assertNull(mAccessibilityCache.getWindowsOnAllDisplays());
     }
 
     @Test
     public void setWindowsThenGetWindows_returnsInDecreasingLayerOrder() {
-        AccessibilityWindowInfo windowInfo1 = null, windowInfo2 = null;
-        AccessibilityWindowInfo window1Out = null, window2Out = null;
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
         List<AccessibilityWindowInfo> windowsOut = null;
         try {
-            windowInfo1 = AccessibilityWindowInfo.obtain();
-            windowInfo1.setId(WINDOW_ID_1);
-            windowInfo1.setLayer(5);
-            windowInfo2 = AccessibilityWindowInfo.obtain();
-            windowInfo2.setId(WINDOW_ID_2);
-            windowInfo2.setLayer(windowInfo1.getLayer() + 1);
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
             List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
-            mAccessibilityCache.setWindows(windowsIn);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
 
-            windowsOut = mAccessibilityCache.getWindows();
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
             window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
             window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
 
@@ -182,8 +191,151 @@
     }
 
     @Test
+    public void setWindowsAndAddWindow_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo window3Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, Display.DEFAULT_DISPLAY,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+
+            assertEquals(3, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(2));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            assertEquals(window3Out, windowsOut.get(1));
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            window3Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void
+            setWindowsAtFirstDisplay_thenAddWindowAtSecondDisplay_returnWindowLayerOrderUnchange() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        List<AccessibilityWindowInfo> windowsOut = null;
+        try {
+            // Sets windows to default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 2);
+            List<AccessibilityWindowInfo> windowsIn = Arrays.asList(windowInfo1, windowInfo2);
+            setWindowsByDisplay(Display.DEFAULT_DISPLAY, windowsIn);
+            // Adds one window to second display.
+            putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_3, SECONDARY_DISPLAY_ID,
+                    windowInfo1.getLayer() + 1);
+
+            windowsOut = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut.size());
+            assertEquals(windowInfo2, windowsOut.get(0));
+            assertEquals(windowInfo1, windowsOut.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void setWindowsAtTwoDisplays_thenGetWindows_returnsInDecreasingLayerOrder() {
+        AccessibilityWindowInfo windowInfo1 = null;
+        AccessibilityWindowInfo windowInfo2 = null;
+        AccessibilityWindowInfo window1Out = null;
+        AccessibilityWindowInfo window2Out = null;
+        AccessibilityWindowInfo windowInfo3 = null;
+        AccessibilityWindowInfo windowInfo4 = null;
+        AccessibilityWindowInfo window3Out = null;
+        AccessibilityWindowInfo window4Out = null;
+        List<AccessibilityWindowInfo> windowsOut1 = null;
+        List<AccessibilityWindowInfo> windowsOut2 = null;
+        try {
+            // Prepares all windows for default display.
+            windowInfo1 = obtainAccessibilityWindowInfo(WINDOW_ID_1, SPECIFIC_WINDOW_LAYER);
+            windowInfo2 = obtainAccessibilityWindowInfo(WINDOW_ID_2, windowInfo1.getLayer() + 1);
+            List<AccessibilityWindowInfo> windowsIn1 = Arrays.asList(windowInfo1, windowInfo2);
+            // Prepares all windows for second display.
+            windowInfo3 = obtainAccessibilityWindowInfo(WINDOW_ID_3, windowInfo1.getLayer() + 2);
+            windowInfo4 = obtainAccessibilityWindowInfo(WINDOW_ID_4, windowInfo1.getLayer() + 3);
+            List<AccessibilityWindowInfo> windowsIn2 = Arrays.asList(windowInfo3, windowInfo4);
+            // Sets all windows of all displays into A11y cache.
+            SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+            allWindows.put(Display.DEFAULT_DISPLAY, windowsIn1);
+            allWindows.put(SECONDARY_DISPLAY_ID, windowsIn2);
+            mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+            // Gets windows at default display.
+            windowsOut1 = getWindowsByDisplay(Display.DEFAULT_DISPLAY);
+            window1Out = mAccessibilityCache.getWindow(WINDOW_ID_1);
+            window2Out = mAccessibilityCache.getWindow(WINDOW_ID_2);
+
+            assertEquals(2, windowsOut1.size());
+            assertEquals(windowInfo2, windowsOut1.get(0));
+            assertEquals(windowInfo1, windowsOut1.get(1));
+            assertEquals(windowInfo1, window1Out);
+            assertEquals(windowInfo2, window2Out);
+            // Gets windows at seocnd display.
+            windowsOut2 = getWindowsByDisplay(SECONDARY_DISPLAY_ID);
+            window3Out = mAccessibilityCache.getWindow(WINDOW_ID_3);
+            window4Out = mAccessibilityCache.getWindow(WINDOW_ID_4);
+
+            assertEquals(2, windowsOut2.size());
+            assertEquals(windowInfo4, windowsOut2.get(0));
+            assertEquals(windowInfo3, windowsOut2.get(1));
+            assertEquals(windowInfo3, window3Out);
+            assertEquals(windowInfo4, window4Out);
+        } finally {
+            window1Out.recycle();
+            window2Out.recycle();
+            windowInfo1.recycle();
+            windowInfo2.recycle();
+            window3Out.recycle();
+            window4Out.recycle();
+            windowInfo3.recycle();
+            windowInfo4.recycle();
+            for (AccessibilityWindowInfo windowInfo : windowsOut1) {
+                windowInfo.recycle();
+            }
+            for (AccessibilityWindowInfo windowInfo : windowsOut2) {
+                windowInfo.recycle();
+            }
+        }
+    }
+
+    @Test
     public void addWindowThenStateChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -191,7 +343,8 @@
 
     @Test
     public void addWindowThenWindowsChangedEvent_noLongerInCache() {
-        putWindowWithIdInCache(WINDOW_ID_1);
+        putWindowWithWindowIdAndDisplayIdInCache(WINDOW_ID_1, Display.DEFAULT_DISPLAY,
+                DEFAULT_WINDOW_LAYER);
         mAccessibilityCache.onAccessibilityEvent(
                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOWS_CHANGED));
         assertNull(mAccessibilityCache.getWindow(WINDOW_ID_1));
@@ -622,9 +775,16 @@
         }
     }
 
-    private void putWindowWithIdInCache(int id) {
+    private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
         AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
-        windowInfo.setId(id);
+        windowInfo.setId(windowId);
+        windowInfo.setLayer(layer);
+        return windowInfo;
+    }
+
+    private void putWindowWithWindowIdAndDisplayIdInCache(int windowId, int displayId, int layer) {
+        AccessibilityWindowInfo windowInfo = obtainAccessibilityWindowInfo(windowId, layer);
+        windowInfo.setDisplayId(displayId);
         mAccessibilityCache.addWindow(windowInfo);
         windowInfo.recycle();
     }
@@ -713,4 +873,20 @@
             }
         }
     }
+
+    private void setWindowsByDisplay(int displayId, List<AccessibilityWindowInfo> windows) {
+        SparseArray<List<AccessibilityWindowInfo>> allWindows = new SparseArray<>();
+        allWindows.put(displayId, windows);
+        mAccessibilityCache.setWindowsOnAllDisplays(allWindows);
+    }
+
+    private List<AccessibilityWindowInfo> getWindowsByDisplay(int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                mAccessibilityCache.getWindowsOnAllDisplays();
+
+        if (allWindows != null && allWindows.size() > 0) {
+            return allWindows.get(displayId);
+        }
+        return null;
+    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 4c59207..1bd52af 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 34;
+    private static final int NUM_MARSHALLED_PROPERTIES = 35;
 
     /**
      * The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 81ce15a..c5da549 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -162,6 +162,11 @@
         }
 
         @Override
+        public void internalNotifySessionLifecycle(boolean started) {
+            throw new UnsupportedOperationException("Should not have been called");
+        }
+
+        @Override
         public void updateContentCaptureContext(ContentCaptureContext context) {
             throw new UnsupportedOperationException("should not have been called");
         }
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
index e1ccd75..8891d3f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/LegacyIntentClassificationFactoryTest.java
@@ -65,8 +65,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
                 InstrumentationRegistry.getContext(),
@@ -101,8 +103,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents = mLegacyIntentClassificationFactory.create(
                 InstrumentationRegistry.getContext(),
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
index b9a1a8c..bcea5fe 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
@@ -83,9 +83,11 @@
                         null,
                         null,
                         null,
+                        null,
                         createRemoteActionTemplates(),
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents =
                 mTemplateClassificationIntentFactory.create(
@@ -124,9 +126,11 @@
                         null,
                         null,
                         null,
+                        null,
                         createRemoteActionTemplates(),
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         List<LabeledIntent> intents =
                 mTemplateClassificationIntentFactory.create(
@@ -162,8 +166,10 @@
                         null,
                         null,
                         null,
-                        0,
-                        0);
+                        null,
+                        0L,
+                        0L,
+                        0d);
 
         mTemplateClassificationIntentFactory.create(
                 InstrumentationRegistry.getContext(),
@@ -196,9 +202,11 @@
                         null,
                         null,
                         null,
+                        null,
                         new RemoteActionTemplate[0],
-                        0,
-                        0);
+                        0L,
+                        0L,
+                        0d);
 
         mTemplateClassificationIntentFactory.create(
                 InstrumentationRegistry.getContext(),
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 767ec0e..cc6eeed 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -24,12 +24,19 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static com.android.internal.app.ChooserActivity.CALLER_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserActivity.SHORTCUT_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_DEFAULT;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
 import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -45,6 +52,8 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager.ShareShortcutInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -472,6 +481,36 @@
     }
 
     @Test
+    public void copyTextToClipboardLogging() throws Exception {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+            Mockito.anyBoolean(),
+            Mockito.anyBoolean(),
+            Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        MetricsLogger mockLogger = sOverrides.metricsLogger;
+        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        onView(withId(R.id.copy_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.copy_button)).perform(click());
+
+        verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
+        // First is  Activity shown, Second is "with preview"
+        assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
+                is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET));
+        assertThat(logMakerCaptor
+                        .getAllValues().get(2)
+                        .getSubtype(),
+                is(1));
+    }
+
+    @Test
     public void oneVisibleImagePreview() throws InterruptedException {
         Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/"
                 + com.android.frameworks.coretests.R.drawable.test320x240);
@@ -770,6 +809,139 @@
         onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
     }
 
+    @Test
+    public void testGetBaseScore() {
+        final float testBaseScore = 0.89f;
+
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+        when(sOverrides.resolverListController.getScore(Mockito.isA(
+                ResolverActivity.DisplayResolveInfo.class))).thenReturn(testBaseScore);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        final ResolverActivity.DisplayResolveInfo testDri =
+                activity.createTestDisplayResolveInfo(sendIntent,
+                ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent);
+        final ChooserActivity.ChooserListAdapter adapter = activity.getAdapter();
+
+        assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_CHOOSER_TARGET), is(testBaseScore));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE),
+                is(SHORTCUT_TARGET_SCORE_BOOST));
+        assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER),
+                is(testBaseScore * SHORTCUT_TARGET_SCORE_BOOST));
+    }
+
+    /**
+     * The case when AppPrediction service is not defined in PackageManager is already covered
+     * as a test parameter {@link ChooserActivityTest#packageManagers}. This test is checking the
+     * case when the prediction service is defined but the component is not available on the device.
+     */
+    @Test
+    public void testIsAppPredictionServiceAvailable() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        if (activity.getPackageManager().getAppPredictionServicePackageName() == null) {
+            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
+        } else {
+            assertThat(activity.isAppPredictionServiceAvailable(), is(true));
+
+            sOverrides.resources = Mockito.spy(activity.getResources());
+            when(sOverrides.resources.getString(R.string.config_defaultAppPredictionService))
+                    .thenReturn("ComponentNameThatDoesNotExist");
+
+            assertThat(activity.isAppPredictionServiceAvailable(), is(false));
+        }
+    }
+
+    @Test
+    public void testConvertToChooserTarget_predictionService() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
+
+        int[] expectedOrderAllShortcuts = {0, 1, 2, 3};
+        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.98f, 0.97f};
+
+        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
+                null, TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderAllShortcuts, expectedScoreAllShortcuts);
+
+        List<ShareShortcutInfo> subset = new ArrayList<>();
+        subset.add(shortcuts.get(1));
+        subset.add(shortcuts.get(2));
+        subset.add(shortcuts.get(3));
+
+        int[] expectedOrderSubset = {1, 2, 3};
+        float[] expectedScoreSubset = {0.99f, 0.98f, 0.97f};
+
+        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
+                TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderSubset, expectedScoreSubset);
+    }
+
+    @Test
+    public void testConvertToChooserTarget_shortcutManager() {
+        Intent sendIntent = createSendTextIntent();
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+        waitForIdle();
+
+        List<ShareShortcutInfo> shortcuts = createShortcuts(activity);
+
+        int[] expectedOrderAllShortcuts = {2, 0, 3, 1};
+        float[] expectedScoreAllShortcuts = {1.0f, 0.99f, 0.99f, 0.98f};
+
+        List<ChooserTarget> chooserTargets = activity.convertToChooserTarget(shortcuts, shortcuts,
+                null, TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderAllShortcuts, expectedScoreAllShortcuts);
+
+        List<ShareShortcutInfo> subset = new ArrayList<>();
+        subset.add(shortcuts.get(1));
+        subset.add(shortcuts.get(2));
+        subset.add(shortcuts.get(3));
+
+        int[] expectedOrderSubset = {2, 3, 1};
+        float[] expectedScoreSubset = {1.0f, 0.99f, 0.98f};
+
+        chooserTargets = activity.convertToChooserTarget(subset, shortcuts, null,
+                TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER);
+        assertCorrectShortcutToChooserTargetConversion(shortcuts, chooserTargets,
+                expectedOrderSubset, expectedScoreSubset);
+    }
+
     // This test is too long and too slow and should not be taken as an example for future tests.
     @Test
     public void testDirectTargetSelectionLogging() throws InterruptedException {
@@ -800,7 +972,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -866,7 +1038,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -927,7 +1099,7 @@
                                 "testInfo",
                                 sendIntent),
                         serviceTargets,
-                        false)
+                        TARGET_TYPE_CHOOSER_TARGET)
         );
         // Thread.sleep shouldn't be a thing in an integration test but it's
         // necessary here because of the way the code is structured
@@ -1066,4 +1238,43 @@
 
         return bitmap;
     }
+
+    private List<ShareShortcutInfo> createShortcuts(Context context) {
+        Intent testIntent = new Intent("TestIntent");
+
+        List<ShareShortcutInfo> shortcuts = new ArrayList<>();
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut1")
+                        .setIntent(testIntent).setShortLabel("label1").setRank(3).build(), // 0  2
+                new ComponentName("package1", "class1")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut2")
+                        .setIntent(testIntent).setShortLabel("label2").setRank(7).build(), // 1  3
+                new ComponentName("package2", "class2")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut3")
+                        .setIntent(testIntent).setShortLabel("label3").setRank(1).build(), // 2  0
+                new ComponentName("package3", "class3")));
+        shortcuts.add(new ShareShortcutInfo(
+                new ShortcutInfo.Builder(context, "shortcut4")
+                        .setIntent(testIntent).setShortLabel("label4").setRank(3).build(), // 3  2
+                new ComponentName("package4", "class4")));
+
+        return shortcuts;
+    }
+
+    private void assertCorrectShortcutToChooserTargetConversion(List<ShareShortcutInfo> shortcuts,
+            List<ChooserTarget> chooserTargets, int[] expectedOrder, float[] expectedScores) {
+        assertEquals(expectedOrder.length, chooserTargets.size());
+        for (int i = 0; i < chooserTargets.size(); i++) {
+            ChooserTarget ct = chooserTargets.get(i);
+            ShortcutInfo si = shortcuts.get(expectedOrder[i]).getShortcutInfo();
+            ComponentName cn = shortcuts.get(expectedOrder[i]).getTargetComponent();
+
+            assertEquals(si.getId(), ct.getIntentExtras().getString(Intent.EXTRA_SHORTCUT_ID));
+            assertEquals(si.getShortLabel(), ct.getTitle());
+            assertThat(Math.abs(expectedScores[i] - ct.getScore()) < 0.000001, is(true));
+            assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 44e56ea..1d567c7 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -85,6 +86,14 @@
     }
 
     @Override
+    public Resources getResources() {
+        if (sOverrides.resources != null) {
+            return sOverrides.resources;
+        }
+        return super.getResources();
+    }
+
+    @Override
     protected Bitmap loadThumbnail(Uri uri, Size size) {
         if (sOverrides.previewThumbnail != null) {
             return sOverrides.previewThumbnail;
@@ -145,6 +154,7 @@
         public Bitmap previewThumbnail;
         public MetricsLogger metricsLogger;
         public int alternateProfileSetting;
+        public Resources resources;
 
         public void reset() {
             onSafelyStartCallback = null;
@@ -157,6 +167,7 @@
             resolverListController = mock(ResolverListController.class);
             metricsLogger = mock(MetricsLogger.class);
             alternateProfileSetting = 0;
+            resources = null;
         }
     }
 }
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index 0a729a9..2141b81 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -194,6 +194,16 @@
         }
 
         @Override
+        public void addHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+        }
+
+        @Override
+        public void removeHdmiControlStatusChangeListener(
+                final IHdmiControlStatusChangeListener listener) {
+        }
+
+        @Override
         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
         }
 
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
new file mode 100644
index 0000000..3d54b68
--- /dev/null
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -0,0 +1,28 @@
+android_test {
+    name: "ScreenshotHelperTests",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+    
+    static_libs: [
+        "frameworks-base-testutils",
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+    ],
+
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    certificate: "platform",
+}
+
diff --git a/core/tests/screenshothelpertests/AndroidManifest.xml b/core/tests/screenshothelpertests/AndroidManifest.xml
new file mode 100644
index 0000000..2e12ef4
--- /dev/null
+++ b/core/tests/screenshothelpertests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          android:installLocation="internalOnly"
+          package="com.android.internal.util"
+          android:sharedUserId="android.uid.systemui" >
+
+    <application >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.internal.util"
+            android:label="Screenshot Helper Tests" />
+
+            <protected-broadcast android:name="android.intent.action.USER_PRESENT" />
+
+</manifest>
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
new file mode 100644
index 0000000..8483645
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotHelperTest {
+    private Context mContext;
+    private ScreenshotHelper mScreenshotHelper;
+    private Handler mHandler;
+
+
+    @Before
+    public void setUp() {
+        // `ScreenshotHelper.notifyScreenshotError()` calls `Context.sendBroadcastAsUser()` and
+        // `Context.bindServiceAsUser`.
+        //
+        // This raises a `SecurityException` if the device is locked. Calling either `Context`
+        // method results in a broadcast of `android.intent.action. USER_PRESENT`. Only the system
+        // process is allowed to broadcast that `Intent`.
+        mContext = Mockito.spy(Context.class);
+        Mockito.doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+        Mockito.doReturn(true).when(mContext).bindServiceAsUser(any(), any(), anyInt(), any());
+
+        mHandler = new Handler(Looper.getMainLooper());
+        mScreenshotHelper = new ScreenshotHelper(mContext);
+    }
+
+    @Test
+    public void testFullscreenScreenshot() {
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, mHandler, null);
+    }
+
+    @Test
+    public void testSelectedRegionScreenshot() {
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_SELECTED_REGION, false, false, mHandler,
+                null);
+    }
+
+    @Test
+    public void testScreenshotTimesOut() {
+        long timeoutMs = 10;
+
+        CountDownLatch lock = new CountDownLatch(1);
+        mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, false, false, timeoutMs,
+                mHandler,
+                worked -> {
+                    assertFalse(worked);
+                    lock.countDown();
+                });
+
+        try {
+            // Add tolerance for delay to prevent flakes.
+            long awaitDurationMs = timeoutMs + 100;
+            if (!lock.await(awaitDurationMs, TimeUnit.MILLISECONDS)) {
+                fail("lock never freed");
+            }
+        } catch (InterruptedException e) {
+            fail("lock interrupted");
+        }
+    }
+}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 9272ea5..c445651 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -128,3 +128,10 @@
     filename_from_src: true,
     product_specific: true,
 }
+
+prebuilt_etc {
+    name: "privapp_whitelist_com.android.car.floatingcardslauncher",
+    sub_dir: "permissions",
+    src: "com.android.car.floatingcardslauncher.xml",
+    filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.floatingcardslauncher.xml b/data/etc/car/com.android.car.floatingcardslauncher.xml
new file mode 100644
index 0000000..2755fee
--- /dev/null
+++ b/data/etc/car/com.android.car.floatingcardslauncher.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<permissions>
+    <privapp-permissions package="com.android.car.floatingcardslauncher">
+        <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.MANAGE_USERS"/>
+        <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_PHONE_STATE"/>
+    </privapp-permissions>
+</permissions>
diff --git a/data/etc/car/com.android.car.settings.xml b/data/etc/car/com.android.car.settings.xml
index 5f7e1c1..291ce80 100644
--- a/data/etc/car/com.android.car.settings.xml
+++ b/data/etc/car/com.android.car.settings.xml
@@ -50,7 +50,6 @@
         <permission name="android.permission.USE_RESERVED_DISK"/>
         <permission name="android.permission.USER_ACTIVITY"/>
         <permission name="android.permission.WRITE_APN_SETTINGS"/>
-        <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
         <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
     </privapp-permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 233f826..2ab7845a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -166,6 +166,7 @@
     <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="cameraserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="cameraserver" />
     <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
+    <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" />
 
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
 
@@ -205,6 +206,10 @@
                       targetSdk="29">
         <new-permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     </split-permission>
+    <split-permission name="android.permission.READ_EXTERNAL_STORAGE"
+                      targetSdk="29">
+        <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" />
+    </split-permission>
 
     <!-- This is a list of all the libraries available for application
          code to link against. -->
@@ -224,6 +229,8 @@
     <library name="android.hidl.manager-V1.0-java"
             file="/system/framework/android.hidl.manager-V1.0-java.jar"
             dependency="android.hidl.base-V1.0-java" />
+    <library name="telephony-common"
+        file="/system/framework/telephony-common.jar" />
 
     <!-- These are the standard packages that are white-listed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
diff --git a/data/keyboards/Vendor_045e_Product_02dd.kl b/data/keyboards/Vendor_045e_Product_02dd.kl
new file mode 100644
index 0000000..3975cec
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02dd.kl
@@ -0,0 +1,57 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# XBox One Controller - Model 1697 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 314   BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315   BUTTON_START
+
+# Xbox key
+key 316   BUTTON_MODE
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index e6eaa696..96ac0f9 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -207,7 +207,7 @@
     }
 
     public GradientDrawable() {
-        this(new GradientState(Orientation.LEFT_RIGHT, null), null);
+        this(new GradientState(Orientation.TOP_BOTTOM, null), null);
     }
 
     /**
@@ -637,7 +637,7 @@
      * @see #setOrientation(Orientation)
      */
     public Orientation getOrientation() {
-        return mGradientState.getOrientation();
+        return mGradientState.mOrientation;
     }
 
     /**
@@ -653,7 +653,7 @@
      * @see #getOrientation()
      */
     public void setOrientation(Orientation orientation) {
-        mGradientState.setOrientation(orientation);
+        mGradientState.mOrientation = orientation;
         mGradientIsDirty = true;
         invalidateSelf();
     }
@@ -1270,7 +1270,7 @@
 
                 if (st.mGradient == LINEAR_GRADIENT) {
                     final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
-                    switch (st.getOrientation()) {
+                    switch (st.mOrientation) {
                     case TOP_BOTTOM:
                         x0 = r.left;            y0 = r.top;
                         x1 = x0;                y1 = level * r.bottom;
@@ -1759,6 +1759,33 @@
         int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
         st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
 
+        switch (st.mAngle) {
+            case 0:
+                st.mOrientation = Orientation.LEFT_RIGHT;
+                break;
+            case 45:
+                st.mOrientation = Orientation.BL_TR;
+                break;
+            case 90:
+                st.mOrientation = Orientation.BOTTOM_TOP;
+                break;
+            case 135:
+                st.mOrientation = Orientation.BR_TL;
+                break;
+            case 180:
+                st.mOrientation = Orientation.RIGHT_LEFT;
+                break;
+            case 225:
+                st.mOrientation = Orientation.TR_BL;
+                break;
+            case 270:
+                st.mOrientation = Orientation.TOP_BOTTOM;
+                break;
+            case 315:
+                st.mOrientation = Orientation.TL_BR;
+                break;
+        }
+
         final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
         if (tv != null) {
             final float radius;
@@ -1981,7 +2008,7 @@
         int[] mAttrPadding;
 
         public GradientState(Orientation orientation, int[] gradientColors) {
-            setOrientation(orientation);
+            mOrientation = orientation;
             setGradientColors(gradientColors);
         }
 
@@ -2184,93 +2211,11 @@
             mCenterY = y;
         }
 
-        public void setOrientation(Orientation orientation) {
-            // Update the angle here so that subsequent attempts to obtain the orientation
-            // from the angle overwrite previously configured values during inflation
-            mAngle = getAngleFromOrientation(orientation);
-            mOrientation = orientation;
-        }
-
         @NonNull
         public Orientation getOrientation() {
-            updateGradientStateOrientation();
             return mOrientation;
         }
 
-        /**
-         * Update the orientation of the gradient based on the given angle only if the type is
-         * {@link #LINEAR_GRADIENT}
-         */
-        private void updateGradientStateOrientation() {
-            if (mGradient == LINEAR_GRADIENT) {
-                int angle = mAngle;
-                if (angle % 45 != 0) {
-                    throw new IllegalArgumentException("Linear gradient requires 'angle' attribute "
-                            + "to be a multiple of 45");
-                }
-
-                Orientation orientation;
-                switch (angle) {
-                    case 0:
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                    case 45:
-                        orientation = Orientation.BL_TR;
-                        break;
-                    case 90:
-                        orientation = Orientation.BOTTOM_TOP;
-                        break;
-                    case 135:
-                        orientation = Orientation.BR_TL;
-                        break;
-                    case 180:
-                        orientation = Orientation.RIGHT_LEFT;
-                        break;
-                    case 225:
-                        orientation = Orientation.TR_BL;
-                        break;
-                    case 270:
-                        orientation = Orientation.TOP_BOTTOM;
-                        break;
-                    case 315:
-                        orientation = Orientation.TL_BR;
-                        break;
-                    default:
-                        // Should not get here as exception is thrown above if angle is not multiple
-                        // of 45 degrees
-                        orientation = Orientation.LEFT_RIGHT;
-                        break;
-                }
-                mOrientation = orientation;
-            }
-        }
-
-        private int getAngleFromOrientation(@Nullable Orientation orientation) {
-            if (orientation != null) {
-                switch (orientation) {
-                    default:
-                    case LEFT_RIGHT:
-                        return 0;
-                    case BL_TR:
-                        return 45;
-                    case BOTTOM_TOP:
-                        return 90;
-                    case BR_TL:
-                        return 135;
-                    case RIGHT_LEFT:
-                        return 180;
-                    case TR_BL:
-                        return 225;
-                    case TOP_BOTTOM:
-                        return 270;
-                    case TL_BR:
-                        return 315;
-                }
-            } else {
-                return 0;
-            }
-        }
-
         public void setGradientColors(@Nullable int[] colors) {
             mGradientColors = colors;
             mSolidColors = null;
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index 08f4176..54995ac 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -16,11 +16,12 @@
 
 package android.security;
 
+import android.annotation.UnsupportedAppUsage;
+
 import com.android.org.bouncycastle.util.io.pem.PemObject;
 import com.android.org.bouncycastle.util.io.pem.PemReader;
 import com.android.org.bouncycastle.util.io.pem.PemWriter;
 
-import android.annotation.UnsupportedAppUsage;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -90,9 +91,9 @@
     public static final String EXTRA_INSTALL_AS_UID = "install_as_uid";
 
     /**
-     * Intent extra: name for the user's private key.
+     * Intent extra: name for the user's key pair.
      */
-    public static final String EXTRA_USER_PRIVATE_KEY_NAME = "user_private_key_name";
+    public static final String EXTRA_USER_KEY_ALIAS = "user_key_pair_name";
 
     /**
      * Intent extra: data for the user's private key in PEM-encoded PKCS#8.
@@ -100,21 +101,11 @@
     public static final String EXTRA_USER_PRIVATE_KEY_DATA = "user_private_key_data";
 
     /**
-     * Intent extra: name for the user's certificate.
-     */
-    public static final String EXTRA_USER_CERTIFICATE_NAME = "user_certificate_name";
-
-    /**
      * Intent extra: data for the user's certificate in PEM-encoded X.509.
      */
     public static final String EXTRA_USER_CERTIFICATE_DATA = "user_certificate_data";
 
     /**
-     * Intent extra: name for CA certificate chain
-     */
-    public static final String EXTRA_CA_CERTIFICATES_NAME = "ca_certificates_name";
-
-    /**
      * Intent extra: data for CA certificate chain in PEM-encoded X.509.
      */
     public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b3cdff7..97da3cc 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -43,7 +43,8 @@
     String installCaCertificate(in byte[] caCertificate);
 
     // APIs used by DevicePolicyManager
-    boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
+    boolean installKeyPair(
+        in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid);
     boolean removeKeyPair(String alias);
 
     // APIs used by Settings
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 54ac0bd..2314072 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -163,7 +163,7 @@
     SkCanvas::SaveLayerFlags layerFlags = 0;
 
     if (!(flags & SaveFlags::ClipToLayer)) {
-        layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
+        layerFlags |= SkCanvasPriv::kDontClipToLayer_SaveLayerFlag;
     }
 
     return layerFlags;
diff --git a/libs/hwui/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 aad15ab..684dc22 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -15,7 +15,17 @@
  */
 
 #include "CanvasContext.h"
+
 #include <GpuMemoryTracker.h>
+#include <apex/window.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
 
 #include "../Properties.h"
 #include "AnimationContext.h"
@@ -32,16 +42,6 @@
 #include "utils/TimeUtils.h"
 #include "utils/TraceUtils.h"
 
-#include <strings.h>
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <algorithm>
-
-#include <cstdint>
-#include <cstdlib>
-#include <functional>
-
 #define TRIM_MEMORY_COMPLETE 80
 #define TRIM_MEMORY_UI_HIDDEN 20
 
@@ -146,7 +146,7 @@
     if (surface) {
         mNativeSurface = new ReliableSurface{std::move(surface)};
         // TODO: Fix error handling & re-shorten timeout
-        mNativeSurface->setDequeueTimeout(4000_ms);
+        ANativeWindow_setDequeueTimeout(mNativeSurface.get(), 4000_ms);
         mNativeSurface->enableFrameTimestamps(true);
     } else {
         mNativeSurface = nullptr;
@@ -484,18 +484,16 @@
         swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC);
         swap.vsyncTime = mRenderThread.timeLord().latestVsync();
         if (didDraw) {
-            int durationUs;
-            nsecs_t dequeueStart = mNativeSurface->getLastDequeueStartTime();
+            nsecs_t dequeueStart = ANativeWindow_getLastDequeueStartTime(mNativeSurface.get());
             if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
                 // Ignoring dequeue duration as it happened prior to frame render start
                 // and thus is not part of the frame.
                 swap.dequeueDuration = 0;
             } else {
-                mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs);
-                swap.dequeueDuration = us2ns(durationUs);
+                swap.dequeueDuration =
+                        us2ns(ANativeWindow_getLastDequeueDuration(mNativeSurface.get()));
             }
-            mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs);
-            swap.queueDuration = us2ns(durationUs);
+            swap.queueDuration = us2ns(ANativeWindow_getLastQueueDuration(mNativeSurface.get()));
         } else {
             swap.dequeueDuration = 0;
             swap.queueDuration = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index a44b804..864780f 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -308,4 +308,4 @@
     return result;
 }
 
-};  // namespace android::uirenderer::renderthread
\ No newline at end of file
+};  // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index 7f1a078..f768df3 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -31,16 +31,12 @@
     ReliableSurface(sp<Surface>&& surface);
     ~ReliableSurface();
 
-    void setDequeueTimeout(nsecs_t timeout) { mSurface->setDequeueTimeout(timeout); }
-
     int reserveNext();
 
     void allocateBuffers() { mSurface->allocateBuffers(); }
 
     int query(int what, int* value) const { return mSurface->query(what, value); }
 
-    nsecs_t getLastDequeueStartTime() const { return mSurface->getLastDequeueStartTime(); }
-
     uint64_t getNextFrameNumber() const { return mSurface->getNextFrameNumber(); }
 
     int getAndClearError() {
@@ -105,4 +101,4 @@
     static int hook_queueBuffer_DEPRECATED(ANativeWindow* window, ANativeWindowBuffer* buffer);
 };
 
-};  // namespace android::uirenderer::renderthread
\ No newline at end of file
+};  // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 8b5912b..12c5b83 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -282,9 +282,9 @@
         return;
     }
     dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
-    dprintf(fd, "\nVersion: %lld", proto->version_code());
-    dprintf(fd, "\nStats since: %lldns", proto->stats_start());
-    dprintf(fd, "\nStats end: %lldns", proto->stats_end());
+    dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
+    dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
+    dprintf(fd, "\nStats end: %" PRId64 "ns", proto->stats_end());
     auto summary = proto->summary();
     dprintf(fd, "\nTotal frames rendered: %d", summary.total_frames());
     dprintf(fd, "\nJanky frames: %d (%.2f%%)", summary.janky_frames(),
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
index a6af475..42bf03e 100644
--- a/libs/protoutil/include/android/util/ProtoOutputStream.h
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -98,6 +98,7 @@
     bool write(uint64_t fieldId, double val);
     bool write(uint64_t fieldId, float val);
     bool write(uint64_t fieldId, int val);
+    bool write(uint64_t fieldId, long val);
     bool write(uint64_t fieldId, long long val);
     bool write(uint64_t fieldId, bool val);
     bool write(uint64_t fieldId, std::string val);
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
index 6cfa357..ea9b79a 100644
--- a/libs/protoutil/src/ProtoOutputStream.cpp
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -116,6 +116,34 @@
 }
 
 bool
+ProtoOutputStream::write(uint64_t fieldId, long val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case FIELD_TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case FIELD_TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case FIELD_TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case FIELD_TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case FIELD_TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case FIELD_TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case FIELD_TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case FIELD_TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case FIELD_TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case FIELD_TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case FIELD_TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case FIELD_TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        default:
+            ALOGW("Field type %d is not supported when writing long val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
 ProtoOutputStream::write(uint64_t fieldId, long long val)
 {
     return internalWrite(fieldId, val, "long long");
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 97bc404..d06ba12 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -107,10 +107,6 @@
     List<LocationRequest> getTestProviderCurrentRequests(String provider, String opPackageName);
     LocationTime getGnssTimeMillis();
 
-    // --- deprecated ---
-    void setTestProviderStatus(String provider, int status, in Bundle extras, long updateTime,
-            String opPackageName);
-
     boolean sendExtraCommand(String provider, String command, inout Bundle extras);
 
     // --- internal ---
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1cc246b..5be4770 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1543,44 +1543,19 @@
     /**
      * This method has no effect as provider status has been deprecated and is no longer supported.
      *
-     * @param provider the provider name
-     * @param status the mock status
-     * @param extras a Bundle containing mock extras
-     * @param updateTime the mock update time
-     *
-     * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
-     * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
-     * allowed} for your app.
-     * @throws IllegalArgumentException if no provider with the given name exists
-     *
      * @deprecated This method has no effect.
      */
     @Deprecated
     public void setTestProviderStatus(
-            @NonNull String provider, int status, @Nullable Bundle extras, long updateTime) {
-        try {
-            mService.setTestProviderStatus(provider, status, extras, updateTime,
-                    mContext.getOpPackageName());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
+            @NonNull String provider, int status, @Nullable Bundle extras, long updateTime) {}
 
     /**
      * This method has no effect as provider status has been deprecated and is no longer supported.
      *
-     * @param provider the provider name
-     * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
-     * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
-     * allowed} for your app.
-     * @throws IllegalArgumentException if no provider with the given name exists
-     *
      * @deprecated This method has no effect.
      */
     @Deprecated
-    public void clearTestProviderStatus(@NonNull String provider) {
-        setTestProviderStatus(provider, LocationProvider.AVAILABLE, null, 0L);
-    }
+    public void clearTestProviderStatus(@NonNull String provider) {}
 
     /**
      * Get the last list of {@link LocationRequest}s sent to the provider.
diff --git a/location/java/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..b36aa03 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,7 +18,7 @@
     name: "com.android.location.provider",
     srcs: [
         "java/**/*.java",
-        ":framework-srcs",
+        ":framework-all-sources",
     ],
     libs: [
         "androidx.annotation_annotation",
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/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index e29e569..7cd09de 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1184,8 +1184,15 @@
             int bufferSizeInBytes, int mode) {
         // If no attributes, OK
         // otherwise check attributes for USAGE_MEDIA and CONTENT_UNKNOWN, MUSIC, or MOVIE.
+        // Only consider flags that are not compatible with FLAG_DEEP_BUFFER. We include
+        // FLAG_DEEP_BUFFER because if set the request is explicit and
+        // shouldEnablePowerSaving() should return false.
+        final int flags = attributes.getAllFlags()
+                & (AudioAttributes.FLAG_DEEP_BUFFER | AudioAttributes.FLAG_LOW_LATENCY
+                    | AudioAttributes.FLAG_HW_AV_SYNC | AudioAttributes.FLAG_BEACON);
+
         if (attributes != null &&
-                (attributes.getAllFlags() != 0  // cannot have any special flags
+                (flags != 0  // cannot have any special flags
                 || attributes.getUsage() != AudioAttributes.USAGE_MEDIA
                 || (attributes.getContentType() != AudioAttributes.CONTENT_TYPE_UNKNOWN
                     && attributes.getContentType() != AudioAttributes.CONTENT_TYPE_MUSIC
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 55583d5..5b53565 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -62,6 +62,7 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
@@ -1451,6 +1452,37 @@
     }
 
     /**
+     * Returns whether ExifInterface currently supports parsing data from the specified mime type
+     * or not.
+     *
+     * @param mimeType the string value of mime type
+     */
+    public static boolean isSupportedMimeType(@NonNull String mimeType) {
+        if (mimeType == null) {
+            throw new NullPointerException("mimeType shouldn't be null");
+        }
+
+        switch (mimeType.toLowerCase(Locale.ROOT)) {
+            case "image/jpeg":
+            case "image/x-adobe-dng":
+            case "image/x-canon-cr2":
+            case "image/x-nikon-nef":
+            case "image/x-nikon-nrw":
+            case "image/x-sony-arw":
+            case "image/x-panasonic-rw2":
+            case "image/x-olympus-orf":
+            case "image/x-pentax-pef":
+            case "image/x-samsung-srw":
+            case "image/x-fuji-raf":
+            case "image/heic":
+            case "image/heif":
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
      * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in
      * the image file.
      *
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ada77c5..dc400ad 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -383,4 +383,20 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Get the hardware sound trigger module properties currently loaded.
+     *
+     * @return The properties currently loaded. Returns null if no supported hardware loaded.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+    @Nullable
+    public SoundTrigger.ModuleProperties getModuleProperties() {
+
+        try {
+            return mSoundTriggerService.getModuleProperties();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c49b1e4..84fe27d 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -52,7 +52,6 @@
         "libandroidfw",
         "libhidlallocatorutils",
         "libhidlbase",
-        "libhidltransport",
         "android.hardware.cas@1.0",
         "android.hardware.cas.native@1.0",
         "android.hidl.memory@1.0",
@@ -147,7 +146,6 @@
         "android.hidl.memory@1.0",
         "libhidlbase",
         "libhidlmemory",
-        "libhidltransport",
         "libbinderthreadstate",
 
         // MediaPlayer2 implementation
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 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/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 4906695..102bbf0 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -951,6 +951,8 @@
         ALOGV("process %p channel %d event %s",
               this, mChannelID, (event == AudioTrack::EVENT_UNDERRUN) ? "UNDERRUN" :
                       "BUFFER_END");
+        // Only BUFFER_END should happen as we use static tracks.
+        setVolume_l(0.f, 0.f);  // set volume to 0 to indicate no need to ramp volume down.
         mSoundPool->addToStopList(this);
     } else if (event == AudioTrack::EVENT_LOOP_END) {
         ALOGV("End loop %p channel %d", this, mChannelID);
@@ -966,14 +968,18 @@
 bool SoundChannel::doStop_l()
 {
     if (mState != IDLE) {
-        setVolume_l(0, 0);
         ALOGV("stop");
-        // Since we're forcibly halting the previously playing content,
-        // we sleep here to ensure the volume is ramped down before we stop the track.
-        // Ideally the sleep time is the mixer period, or an approximation thereof
-        // (Fast vs Normal tracks are different).
-        // TODO: consider pausing instead of stop here.
-        std::this_thread::sleep_for(std::chrono::milliseconds(20));
+        if (mLeftVolume != 0.f || mRightVolume != 0.f) {
+            setVolume_l(0.f, 0.f);
+            if (mSoundPool->attributes()->usage != AUDIO_USAGE_GAME) {
+                // Since we're forcibly halting the previously playing content,
+                // we sleep here to ensure the volume is ramped down before we stop the track.
+                // Ideally the sleep time is the mixer period, or an approximation thereof
+                // (Fast vs Normal tracks are different).
+                ALOGV("sleeping: ChannelID:%d  SampleID:%d", mChannelID, mSample->sampleID());
+                std::this_thread::sleep_for(std::chrono::milliseconds(20));
+            }
+        }
         mAudioTrack->stop();
         mPrevSampleID = mSample->sampleID();
         mSample.clear();
diff --git a/media/jni/soundpool/SoundPool.h b/media/jni/soundpool/SoundPool.h
index 9d74103..01e4faa 100644
--- a/media/jni/soundpool/SoundPool.h
+++ b/media/jni/soundpool/SoundPool.h
@@ -61,7 +61,7 @@
     audio_channel_mask_t channelMask() { return mChannelMask; }
     size_t size() { return mSize; }
     int state() { return mState; }
-    uint8_t* data() { return static_cast<uint8_t*>(mData->pointer()); }
+    uint8_t* data() { return static_cast<uint8_t*>(mData->unsecurePointer()); }
     status_t doLoad();
     void startLoad() { mState = LOADING; }
     sp<IMemory> getIMemory() { return mData; }
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 85a007f..2286c53 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,7 +18,7 @@
     name: "com.android.mediadrm.signer",
     srcs: [
         "java/**/*.java",
-        ":framework-srcs",
+        ":framework-all-sources",
     ],
     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/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
new file mode 100644
index 0000000..9bcd677
--- /dev/null
+++ b/packages/BackupEncryption/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_app {
+    name: "BackupEncryption",
+    srcs: ["src/**/*.java"],
+    libs: ["backup-encryption-protos"],
+    optimize: { enabled: false },
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
+
+java_library {
+    name: "backup-encryption-protos",
+    proto: { type: "nano" },
+    srcs: ["proto/**/*.proto"],
+}
diff --git a/packages/BackupEncryption/AndroidManifest.xml b/packages/BackupEncryption/AndroidManifest.xml
new file mode 100644
index 0000000..a705df5
--- /dev/null
+++ b/packages/BackupEncryption/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.backup.encryption"
+    android:sharedUserId="android.uid.system" >
+
+    <application android:allowBackup="false" />
+</manifest>
diff --git a/packages/BackupEncryption/proguard.flags b/packages/BackupEncryption/proguard.flags
new file mode 100644
index 0000000..851ce8c
--- /dev/null
+++ b/packages/BackupEncryption/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.server.backup.encryption
diff --git a/core/proto/android/server/backup_chunks_metadata.proto b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
similarity index 97%
rename from core/proto/android/server/backup_chunks_metadata.proto
rename to packages/BackupEncryption/proto/backup_chunks_metadata.proto
index a375f02..2fdedbf 100644
--- a/core/proto/android/server/backup_chunks_metadata.proto
+++ b/packages/BackupEncryption/proto/backup_chunks_metadata.proto
@@ -15,8 +15,10 @@
  */
 
 syntax = "proto2";
-package com.android.server.backup.encryption.chunk;
 
+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
diff --git a/packages/BackupEncryption/proto/key_value_listing.proto b/packages/BackupEncryption/proto/key_value_listing.proto
new file mode 100644
index 0000000..001e697
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_listing.proto
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValueListingProto";
+
+// An entry of a key-value pair.
+message KeyValueEntry {
+  // Plaintext key of the key-value pair.
+  optional string key = 1;
+  // SHA-256 MAC of the plaintext of the chunk containing the pair
+  optional bytes hash = 2;
+}
+
+// Describes the key/value pairs currently in the backup blob, mapping from the
+// plaintext key to the hash of the chunk containing the pair.
+//
+// This is local state stored on the device. It is never sent to the
+// backup server. See ChunkOrdering for how the device restores the
+// key-value pairs in the correct order.
+message KeyValueListing {
+  repeated KeyValueEntry entries = 1;
+}
diff --git a/packages/BackupEncryption/proto/key_value_pair.proto b/packages/BackupEncryption/proto/key_value_pair.proto
new file mode 100644
index 0000000..177fa30
--- /dev/null
+++ b/packages/BackupEncryption/proto/key_value_pair.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "KeyValuePairProto";
+
+// Serialized form of a key-value pair, when it is to be encrypted in a blob.
+// The backup blob for a key-value database consists of repeated encrypted
+// key-value pairs like this, in a randomized order. See ChunkOrdering for how
+// these are then reconstructed during a restore.
+message KeyValuePair {
+  optional string key = 1;
+  optional bytes value = 2;
+}
diff --git a/packages/BackupEncryption/proto/wrapped_key.proto b/packages/BackupEncryption/proto/wrapped_key.proto
new file mode 100644
index 0000000..817b7b4
--- /dev/null
+++ b/packages/BackupEncryption/proto/wrapped_key.proto
@@ -0,0 +1,52 @@
+syntax = "proto2";
+
+package android_backup_crypto;
+
+option java_package = "com.android.server.backup.encryption.protos";
+option java_outer_classname = "WrappedKeyProto";
+
+// Metadata associated with a tertiary key.
+message KeyMetadata {
+  // Type of Cipher algorithm the key is used for.
+  enum Type {
+    UNKNOWN = 0;
+    // No padding. Uses 12-byte nonce. Tag length 16 bytes.
+    AES_256_GCM = 1;
+  }
+
+  // What kind of Cipher algorithm the key is used for. We assume at the moment
+  // that this will always be AES_256_GCM and throw if this is not the case.
+  // Provided here for forwards compatibility in case at some point we need to
+  // change Cipher algorithm.
+  optional Type type = 1;
+}
+
+// An encrypted tertiary key.
+message WrappedKey {
+  // The Cipher with which the key was encrypted.
+  enum WrapAlgorithm {
+    UNKNOWN = 0;
+    // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes.
+    // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore.
+    // AndroidKeyStore requires that it generates the IV, and it generates a
+    // 16-byte IV for you. You CANNOT provide your own IV.
+    AES_256_GCM = 1;
+  }
+
+  // Cipher algorithm used to wrap the key. We assume at the moment that this
+  // is always AES_256_GC and throw if this is not the case. Provided here for
+  // forwards compatibility if at some point we need to change Cipher algorithm.
+  optional WrapAlgorithm wrap_algorithm = 1;
+
+  // The nonce used to initialize the Cipher in AES/256/GCM mode.
+  optional bytes nonce = 2;
+
+  // The encrypted bytes of the key material.
+  optional bytes key = 3;
+
+  // Associated key metadata.
+  optional KeyMetadata metadata = 4;
+
+  // Deprecated field; Do not use
+  reserved 5;
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
new file mode 100644
index 0000000..033f1b1
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkState;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.KeyStoreException;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * State about encrypted backups that needs to be remembered.
+ */
+public class CryptoSettings {
+
+    private static final String TAG = "CryptoSettings";
+
+    private static final String SHARED_PREFERENCES_NAME = "crypto_settings";
+
+    private static final String KEY_IS_INITIALIZED = "isInitialized";
+    private static final String KEY_ACTIVE_SECONDARY_ALIAS = "activeSecondary";
+    private static final String KEY_NEXT_SECONDARY_ALIAS = "nextSecondary";
+    private static final String SECONDARY_KEY_LAST_ROTATED_AT = "secondaryKeyLastRotatedAt";
+    private static final String[] SETTINGS_FOR_BACKUP = {
+        KEY_IS_INITIALIZED,
+        KEY_ACTIVE_SECONDARY_ALIAS,
+        KEY_NEXT_SECONDARY_ALIAS,
+        SECONDARY_KEY_LAST_ROTATED_AT
+    };
+
+    private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD =
+            TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS);
+
+    private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION =
+            "ancestral_secondary_key_version";
+
+    private final SharedPreferences mSharedPreferences;
+    private final Context mContext;
+
+    /**
+     * A new instance.
+     *
+     * @param context For looking up the {@link SharedPreferences}, for storing state.
+     * @return The instance.
+     */
+    public static CryptoSettings getInstance(Context context) {
+        // We need single process mode because CryptoSettings may be used from several processes
+        // simultaneously.
+        SharedPreferences sharedPreferences =
+                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+        return new CryptoSettings(sharedPreferences, context);
+    }
+
+    /**
+     * A new instance using {@link SharedPreferences} in the default mode.
+     *
+     * <p>This will not work across multiple processes but will work in tests.
+     */
+    @VisibleForTesting
+    public static CryptoSettings getInstanceForTesting(Context context) {
+        SharedPreferences sharedPreferences =
+                context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+        return new CryptoSettings(sharedPreferences, context);
+    }
+
+    private CryptoSettings(SharedPreferences sharedPreferences, Context context) {
+        mSharedPreferences = checkNotNull(sharedPreferences);
+        mContext = checkNotNull(context);
+    }
+
+    /**
+     * The alias of the current active secondary key. This should be used to retrieve the key from
+     * AndroidKeyStore.
+     */
+    public Optional<String> getActiveSecondaryKeyAlias() {
+        return getStringInSharedPrefs(KEY_ACTIVE_SECONDARY_ALIAS);
+    }
+
+    /**
+     * The alias of the secondary key to which the client is rotating. The rotation is not
+     * immediate, which is why this setting is needed. Once the next key is created, it can take up
+     * to 72 hours potentially (or longer if the user has no network) for the next key to be synced
+     * with the keystore. Only after that has happened does the client attempt to re-wrap all
+     * tertiary keys and commit the rotation.
+     */
+    public Optional<String> getNextSecondaryKeyAlias() {
+        return getStringInSharedPrefs(KEY_NEXT_SECONDARY_ALIAS);
+    }
+
+    /**
+     * If the settings have been initialized.
+     */
+    public boolean getIsInitialized() {
+        return mSharedPreferences.getBoolean(KEY_IS_INITIALIZED, false);
+    }
+
+    /**
+     * Sets the alias of the currently active secondary key.
+     *
+     * @param activeAlias The alias, as in AndroidKeyStore.
+     * @throws IllegalArgumentException if the alias is not in the user's keystore.
+     */
+    public void setActiveSecondaryKeyAlias(String activeAlias) throws IllegalArgumentException {
+        assertIsValidAlias(activeAlias);
+        mSharedPreferences.edit().putString(KEY_ACTIVE_SECONDARY_ALIAS, activeAlias).apply();
+    }
+
+    /**
+     * Sets the alias of the secondary key to which the client is rotating.
+     *
+     * @param nextAlias The alias, as in AndroidKeyStore.
+     * @throws KeyStoreException if unable to check whether alias is valid in the keystore.
+     * @throws IllegalArgumentException if the alias is not in the user's keystore.
+     */
+    public void setNextSecondaryAlias(String nextAlias) throws IllegalArgumentException {
+        assertIsValidAlias(nextAlias);
+        mSharedPreferences.edit().putString(KEY_NEXT_SECONDARY_ALIAS, nextAlias).apply();
+    }
+
+    /**
+     * Unsets the alias of the key to which the client is rotating. This is generally performed once
+     * a rotation is complete.
+     */
+    public void removeNextSecondaryKeyAlias() {
+        mSharedPreferences.edit().remove(KEY_NEXT_SECONDARY_ALIAS).apply();
+    }
+
+    /**
+     * Sets the timestamp of when the secondary key was last rotated.
+     *
+     * @param timestamp The timestamp to set.
+     */
+    public void setSecondaryLastRotated(long timestamp) {
+        mSharedPreferences.edit().putLong(SECONDARY_KEY_LAST_ROTATED_AT, timestamp).apply();
+    }
+
+    /**
+     * Returns a timestamp of when the secondary key was last rotated.
+     *
+     * @return The timestamp.
+     */
+    public Optional<Long> getSecondaryLastRotated() {
+        if (!mSharedPreferences.contains(SECONDARY_KEY_LAST_ROTATED_AT)) {
+            return Optional.empty();
+        }
+        return Optional.of(mSharedPreferences.getLong(SECONDARY_KEY_LAST_ROTATED_AT, -1));
+    }
+
+    /**
+     * Sets the settings to have been initialized. (Otherwise loading should try to initialize
+     * again.)
+     */
+    private void setIsInitialized() {
+        mSharedPreferences.edit().putBoolean(KEY_IS_INITIALIZED, true).apply();
+    }
+
+    /**
+     * Initializes with the given key alias.
+     *
+     * @param alias The secondary key alias to be set as active.
+     * @throws IllegalArgumentException if the alias does not reference a valid key.
+     * @throws IllegalStateException if attempting to initialize an already initialized settings.
+     */
+    public void initializeWithKeyAlias(String alias) throws IllegalArgumentException {
+        checkState(
+                !getIsInitialized(), "Attempting to initialize an already initialized settings.");
+        setActiveSecondaryKeyAlias(alias);
+        setIsInitialized();
+    }
+
+    /** Returns the secondary key version of the encrypted backup set to restore from (if set). */
+    public Optional<String> getAncestralSecondaryKeyVersion() {
+        return Optional.ofNullable(
+                mSharedPreferences.getString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, null));
+    }
+
+    /** Sets the secondary key version of the encrypted backup set to restore from. */
+    public void setAncestralSecondaryKeyVersion(String ancestralSecondaryKeyVersion) {
+        mSharedPreferences
+                .edit()
+                .putString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, ancestralSecondaryKeyVersion)
+                .apply();
+    }
+
+    /** The number of milliseconds between secondary key rotation */
+    public long backupSecondaryKeyRotationIntervalMs() {
+        return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD;
+    }
+
+    /** Deletes all crypto settings related to backup (as opposed to restore). */
+    public void clearAllSettingsForBackup() {
+        Editor sharedPrefsEditor = mSharedPreferences.edit();
+        for (String backupSettingKey : SETTINGS_FOR_BACKUP) {
+            sharedPrefsEditor.remove(backupSettingKey);
+        }
+        sharedPrefsEditor.apply();
+
+        Slog.d(TAG, "Cleared crypto settings for backup");
+    }
+
+    /**
+     * Throws {@link IllegalArgumentException} if the alias does not refer to a key that is in
+     * the {@link RecoveryController}.
+     */
+    private void assertIsValidAlias(String alias) throws IllegalArgumentException {
+        try {
+            if (!RecoveryController.getInstance(mContext).getAliases().contains(alias)) {
+                throw new IllegalArgumentException(alias + " is not in RecoveryController");
+            }
+        } catch (InternalRecoveryServiceException e) {
+            throw new IllegalArgumentException("Problem accessing recovery service", e);
+        }
+    }
+
+    private Optional<String> getStringInSharedPrefs(String key) {
+        return Optional.ofNullable(mSharedPreferences.getString(key, null));
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java
similarity index 98%
rename from services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java
index 1ae598e..1630eb8 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkHash.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java
@@ -11,12 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunk;
 
 import com.android.internal.util.Preconditions;
+
 import java.util.Arrays;
 import java.util.Base64;
 
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
similarity index 64%
rename from services/backup/java/com/android/server/backup/encryption/chunk/ChunkListingMap.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java
index a448901..51d7d53 100644
--- a/services/backup/java/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/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
similarity index 74%
rename from services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
index df36c94..9cda339 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java
@@ -11,16 +11,17 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.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;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
similarity index 96%
rename from services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
index 3a6d1f6..edf1b9a 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunk;
@@ -38,6 +38,7 @@
 
     private final byte[] mEncryptedChunkOrdering;
 
+    /** Get the encrypted chunk ordering */
     public byte[] encryptedChunkOrdering() {
         return mEncryptedChunkOrdering;
     }
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java
similarity index 97%
rename from services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java
index 68d9d14..baa820c 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/BackupWriter.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/ByteRange.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
similarity index 98%
rename from services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
index 812cfbd..48abc8c 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java
@@ -11,16 +11,18 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
 
 import com.android.server.backup.encryption.chunk.ChunkHash;
+
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java
similarity index 97%
rename from services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java
index 145b7bf..02d498c 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java
@@ -11,14 +11,16 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
 
 import com.android.server.backup.encryption.chunk.ChunkHash;
+
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
 
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java
similarity index 97%
rename from services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java
index b91913e..c9a6293 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java
similarity index 98%
rename from services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java
index 1f936eb..cde59fa 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunk.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java
@@ -11,13 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
 
 import com.android.internal.util.Preconditions;
 import com.android.server.backup.encryption.chunk.ChunkHash;
+
 import java.util.Arrays;
 import java.util.Objects;
 
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
similarity index 97%
rename from services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
index eaf701c..16beda3 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java
@@ -11,12 +11,13 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
 
 import com.android.server.backup.encryption.chunk.ChunkOrderingType;
+
 import java.io.IOException;
 
 /** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
similarity index 95%
rename from services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
index 5c902ca..6b9be9f 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java
@@ -11,13 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.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/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
similarity index 94%
rename from services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
index 4b84981..e707350 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java
@@ -11,13 +11,14 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.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/services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java
similarity index 82%
rename from services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java
index 839dc7c..b211b0f 100644
--- a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
@@ -21,18 +21,18 @@
 
 /** Writes data straight to an output stream. */
 public class RawBackupWriter implements BackupWriter {
-    private final OutputStream outputStream;
-    private long bytesWritten;
+    private final OutputStream mOutputStream;
+    private long mBytesWritten;
 
     /** Constructs a new writer which writes bytes to the given output stream. */
     public RawBackupWriter(OutputStream outputStream) {
-        this.outputStream = outputStream;
+        this.mOutputStream = outputStream;
     }
 
     @Override
     public void writeBytes(byte[] bytes) throws IOException {
-        outputStream.write(bytes);
-        bytesWritten += bytes.length;
+        mOutputStream.write(bytes);
+        mBytesWritten += bytes.length;
     }
 
     @Override
@@ -42,11 +42,11 @@
 
     @Override
     public long getBytesWritten() {
-        return bytesWritten;
+        return mBytesWritten;
     }
 
     @Override
     public void flush() throws IOException {
-        outputStream.flush();
+        mOutputStream.flush();
     }
 }
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
new file mode 100644
index 0000000..a043c1f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+
+/** Utility functions for wrapping and unwrapping tertiary keys. */
+public class KeyWrapUtils {
+    private static final String AES_GCM_MODE = "AES/GCM/NoPadding";
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+    private static final int BITS_PER_BYTE = 8;
+    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
+    private static final String KEY_ALGORITHM = "AES";
+
+    /**
+     * Uses the secondary key to unwrap the wrapped tertiary key.
+     *
+     * @param secondaryKey The secondary key used to wrap the tertiary key.
+     * @param wrappedKey The wrapped tertiary key.
+     * @return The unwrapped tertiary key.
+     * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key.
+     */
+    public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey)
+            throws InvalidKeyException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException, NoSuchPaddingException {
+        if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) {
+            throw new InvalidKeyException(
+                    String.format(
+                            Locale.US,
+                            "Could not unwrap key wrapped with %s algorithm",
+                            wrappedKey.wrapAlgorithm));
+        }
+
+        if (wrappedKey.metadata == null) {
+            throw new InvalidKeyException("Metadata missing from wrapped tertiary key.");
+        }
+
+        if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) {
+            throw new InvalidKeyException(
+                    String.format(
+                            Locale.US,
+                            "Wrapped key was unexpected %s algorithm. Only support"
+                                + " AES/GCM/NoPadding.",
+                            wrappedKey.metadata.type));
+        }
+
+        Cipher cipher = getCipher();
+
+        cipher.init(
+                Cipher.UNWRAP_MODE,
+                secondaryKey,
+                new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce));
+
+        return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY);
+    }
+
+    /**
+     * Wraps the tertiary key with the secondary key.
+     *
+     * @param secondaryKey The secondary key to use for wrapping.
+     * @param tertiaryKey The key to wrap.
+     * @return The wrapped key.
+     * @throws InvalidKeyException if the key is not good for wrapping.
+     * @throws IllegalBlockSizeException if there is an issue wrapping.
+     */
+    public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey)
+            throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        Cipher cipher = getCipher();
+        cipher.init(Cipher.WRAP_MODE, secondaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
+        wrappedKey.key = cipher.wrap(tertiaryKey);
+        wrappedKey.nonce = cipher.getIV();
+        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM;
+        wrappedKey.metadata = new WrappedKeyProto.KeyMetadata();
+        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM;
+        return wrappedKey;
+    }
+
+    /**
+     * Rewraps a tertiary key with a new secondary key.
+     *
+     * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key.
+     * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key.
+     * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}.
+     * @return The tertiary key, wrapped by {@code newSecondaryKey}.
+     * @throws InvalidKeyException if the key is not good for wrapping or unwrapping.
+     * @throws IllegalBlockSizeException if there is an issue wrapping.
+     */
+    public static WrappedKeyProto.WrappedKey rewrap(
+            SecretKey oldSecondaryKey,
+            SecretKey newSecondaryKey,
+            WrappedKeyProto.WrappedKey tertiaryKey)
+            throws InvalidKeyException, IllegalBlockSizeException,
+                    InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey));
+    }
+
+    private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+        return Cipher.getInstance(AES_GCM_MODE);
+    }
+
+    // Statics only
+    private KeyWrapUtils() {}
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java
new file mode 100644
index 0000000..6fb958b
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager.RecoverableKeyStoreSecondaryKeyManagerProvider;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/** Fetches the secondary key and uses it to unwrap the tertiary key during restore. */
+public class RestoreKeyFetcher {
+
+    /**
+     * Retrieves the secondary key with the given alias and uses it to unwrap the given wrapped
+     * tertiary key.
+     *
+     * @param secondaryKeyManagerProvider Provider which creates {@link
+     *     RecoverableKeyStoreSecondaryKeyManager}
+     * @param secondaryKeyAlias Alias of the secondary key used to wrap the tertiary key
+     * @param wrappedTertiaryKey Tertiary key wrapped with the secondary key above
+     * @return The unwrapped tertiary key
+     */
+    public static SecretKey unwrapTertiaryKey(
+            RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider,
+            String secondaryKeyAlias,
+            WrappedKeyProto.WrappedKey wrappedTertiaryKey)
+            throws KeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        Optional<RecoverableKeyStoreSecondaryKey> secondaryKey =
+                getSecondaryKey(secondaryKeyManagerProvider, secondaryKeyAlias);
+        if (!secondaryKey.isPresent()) {
+            throw new KeyException("No key:" + secondaryKeyAlias);
+        }
+
+        return KeyWrapUtils.unwrap(secondaryKey.get().getSecretKey(), wrappedTertiaryKey);
+    }
+
+    private static Optional<RecoverableKeyStoreSecondaryKey> getSecondaryKey(
+            RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider,
+            String secondaryKeyAlias)
+            throws KeyException {
+        try {
+            return secondaryKeyManagerProvider.get().get(secondaryKeyAlias);
+        } catch (InternalRecoveryServiceException | UnrecoverableKeyException e) {
+            throw new KeyException("Could not retrieve key:" + secondaryKeyAlias, e);
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
new file mode 100644
index 0000000..91b57cf
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import java.io.File;
+import java.time.Clock;
+import java.util.Optional;
+
+/**
+ * Helps schedule rotations of secondary keys.
+ *
+ * <p>TODO(b/72028016) Replace with a job.
+ */
+public class SecondaryKeyRotationScheduler {
+
+    private static final String TAG = "SecondaryKeyRotationScheduler";
+    private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+    private final Context mContext;
+    private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    private final CryptoSettings mCryptoSettings;
+    private final Clock mClock;
+
+    public SecondaryKeyRotationScheduler(
+            Context context,
+            RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager,
+            CryptoSettings cryptoSettings,
+            Clock clock) {
+        mContext = context;
+        mCryptoSettings = cryptoSettings;
+        mClock = clock;
+        mSecondaryKeyManager = secondaryKeyManager;
+    }
+
+    /**
+     * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This
+     * is only for testing purposes.
+     */
+    private boolean isForceRotationTestSentinelPresent() {
+        File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH);
+        if (file.exists()) {
+            file.delete();
+            return true;
+        }
+        return false;
+    }
+
+    /** Start the key rotation task if it's time to do so */
+    public void startRotationIfScheduled() {
+        if (isForceRotationTestSentinelPresent()) {
+            Slog.i(TAG, "Found force flag for secondary rotation. Starting now.");
+            startRotation();
+            return;
+        }
+
+        Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated();
+        if (!maybeLastRotated.isPresent()) {
+            Slog.v(TAG, "No previous rotation, scheduling from now.");
+            scheduleRotationFromNow();
+            return;
+        }
+
+        long lastRotated = maybeLastRotated.get();
+        long now = mClock.millis();
+
+        if (lastRotated > now) {
+            Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now.");
+            startRotation();
+            return;
+        }
+
+        long millisSinceLastRotation = now - lastRotated;
+        long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+        if (millisSinceLastRotation >= rotationInterval) {
+            Slog.i(
+                    TAG,
+                    "Last rotation was more than "
+                            + rotationInterval
+                            + "ms ("
+                            + millisSinceLastRotation
+                            + "ms) in the past. Rotate now.");
+            startRotation();
+        }
+
+        Slog.v(TAG, "No rotation required, last " + lastRotated + ".");
+    }
+
+    private void startRotation() {
+        scheduleRotationFromNow();
+        new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run();
+    }
+
+    private void scheduleRotationFromNow() {
+        mCryptoSettings.setSecondaryLastRotated(mClock.millis());
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java
new file mode 100644
index 0000000..a783579
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Optional;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Gets the correct tertiary key to use during a backup, rotating it if required.
+ *
+ * <p>Calling any method on this class will count a incremental backup against the app, and the key
+ * will be rotated if required.
+ */
+public class TertiaryKeyManager {
+
+    private static final String TAG = "TertiaryKeyMgr";
+
+    private final TertiaryKeyStore mKeyStore;
+    private final TertiaryKeyGenerator mKeyGenerator;
+    private final TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler;
+    private final RecoverableKeyStoreSecondaryKey mSecondaryKey;
+    private final String mPackageName;
+
+    private boolean mKeyRotated;
+    @Nullable private SecretKey mTertiaryKey;
+
+    public TertiaryKeyManager(
+            Context context,
+            SecureRandom secureRandom,
+            TertiaryKeyRotationScheduler tertiaryKeyRotationScheduler,
+            RecoverableKeyStoreSecondaryKey secondaryKey,
+            String packageName) {
+        mSecondaryKey = secondaryKey;
+        mPackageName = packageName;
+        mKeyGenerator = new TertiaryKeyGenerator(secureRandom);
+        mKeyStore = TertiaryKeyStore.newInstance(context, secondaryKey);
+        mTertiaryKeyRotationScheduler = tertiaryKeyRotationScheduler;
+    }
+
+    /**
+     * Returns either the previously used tertiary key, or a new tertiary key if there was no
+     * previous key or it needed to be rotated.
+     */
+    public SecretKey getKey()
+            throws InvalidKeyException, IOException, IllegalBlockSizeException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return mTertiaryKey;
+    }
+
+    /** Returns the key given by {@link #getKey()} wrapped by the secondary key. */
+    public WrappedKeyProto.WrappedKey getWrappedKey()
+            throws InvalidKeyException, IOException, IllegalBlockSizeException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return KeyWrapUtils.wrap(mSecondaryKey.getSecretKey(), mTertiaryKey);
+    }
+
+    /**
+     * Returns {@code true} if a new tertiary key was generated at the start of this session,
+     * otherwise {@code false}.
+     */
+    public boolean wasKeyRotated()
+            throws InvalidKeyException, IllegalBlockSizeException, IOException,
+                    NoSuchPaddingException, NoSuchAlgorithmException,
+                    InvalidAlgorithmParameterException {
+        init();
+        return mKeyRotated;
+    }
+
+    private void init()
+            throws IllegalBlockSizeException, InvalidKeyException, IOException,
+                    NoSuchAlgorithmException, NoSuchPaddingException,
+                    InvalidAlgorithmParameterException {
+        if (mTertiaryKey != null) {
+            return;
+        }
+
+        Optional<SecretKey> key = getExistingKeyIfNotRotated();
+
+        if (!key.isPresent()) {
+            Slog.d(TAG, "Generating new tertiary key for " + mPackageName);
+
+            key = Optional.of(mKeyGenerator.generate());
+            mKeyRotated = true;
+            mTertiaryKeyRotationScheduler.recordKeyRotation(mPackageName);
+            mKeyStore.save(mPackageName, key.get());
+        }
+
+        mTertiaryKey = key.get();
+
+        mTertiaryKeyRotationScheduler.recordBackup(mPackageName);
+    }
+
+    private Optional<SecretKey> getExistingKeyIfNotRotated()
+            throws InvalidKeyException, IOException, InvalidAlgorithmParameterException,
+                    NoSuchAlgorithmException, NoSuchPaddingException {
+        if (mTertiaryKeyRotationScheduler.isKeyRotationDue(mPackageName)) {
+            Slog.i(TAG, "Tertiary key rotation was required for " + mPackageName);
+            return Optional.empty();
+        } else {
+            Slog.i(TAG, "Tertiary key rotation was not required");
+            return mKeyStore.load(mPackageName);
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java
new file mode 100644
index 0000000..f16a68d
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Schedules tertiary key rotations in a staggered fashion.
+ *
+ * <p>Apps are due a key rotation after a certain number of backups. Rotations are then staggerered
+ * over a period of time, through restricting the number of rotations allowed in a 24-hour window.
+ * This will causes the apps to enter a staggered cycle of regular rotations.
+ *
+ * <p>Note: the methods in this class are not optimized to be super fast. They make blocking IO to
+ * ensure that scheduler information is committed to disk, so that it is available after the user
+ * turns their device off and on. This ought to be fine as
+ *
+ * <ul>
+ *   <li>It will be invoked before a backup, so should never be invoked on the UI thread
+ *   <li>It will be invoked before a backup, so the vast amount of time is spent on the backup, not
+ *       writing tiny amounts of data to disk.
+ * </ul>
+ */
+public class TertiaryKeyRotationScheduler {
+    /** Default number of key rotations allowed within 24 hours. */
+    private static final int KEY_ROTATION_LIMIT = 2;
+
+    /** A new instance, using {@code context} to determine where to store state. */
+    public static TertiaryKeyRotationScheduler getInstance(Context context) {
+        TertiaryKeyRotationWindowedCount windowedCount =
+                TertiaryKeyRotationWindowedCount.getInstance(context);
+        TertiaryKeyRotationTracker tracker = TertiaryKeyRotationTracker.getInstance(context);
+        return new TertiaryKeyRotationScheduler(tracker, windowedCount, KEY_ROTATION_LIMIT);
+    }
+
+    private final TertiaryKeyRotationTracker mTracker;
+    private final TertiaryKeyRotationWindowedCount mWindowedCount;
+    private final int mMaximumRotationsPerWindow;
+
+    /**
+     * A new instance.
+     *
+     * @param tracker Tracks how many times each application has backed up.
+     * @param windowedCount Tracks how many rotations have happened in the last 24 hours.
+     * @param maximumRotationsPerWindow The maximum number of key rotations allowed per 24 hours.
+     */
+    @VisibleForTesting
+    TertiaryKeyRotationScheduler(
+            TertiaryKeyRotationTracker tracker,
+            TertiaryKeyRotationWindowedCount windowedCount,
+            int maximumRotationsPerWindow) {
+        mTracker = tracker;
+        mWindowedCount = windowedCount;
+        mMaximumRotationsPerWindow = maximumRotationsPerWindow;
+    }
+
+    /**
+     * Returns {@code true} if the app with {@code packageName} is due having its key rotated.
+     *
+     * <p>This ought to be queried before backing up an app, to determine whether to do an
+     * incremental backup or a full backup. (A full backup forces key rotation.)
+     */
+    public boolean isKeyRotationDue(String packageName) {
+        if (mWindowedCount.getCount() >= mMaximumRotationsPerWindow) {
+            return false;
+        }
+        return mTracker.isKeyRotationDue(packageName);
+    }
+
+    /**
+     * Records that a backup happened for the app with the given {@code packageName}.
+     *
+     * <p>Each backup brings the app closer to the point at which a key rotation is due.
+     */
+    public void recordBackup(String packageName) {
+        mTracker.recordBackup(packageName);
+    }
+
+    /**
+     * Records a key rotation happened for the app with the given {@code packageName}.
+     *
+     * <p>This resets the countdown until the next key rotation is due.
+     */
+    public void recordKeyRotation(String packageName) {
+        mTracker.resetCountdown(packageName);
+        mWindowedCount.record();
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
similarity index 80%
rename from services/backup/java/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java
index ec90f6c..1a281e7 100644
--- a/services/backup/java/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/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/EncryptionDbException.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/TertiaryKey.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java
diff --git a/services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/BackupEncrypter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java
diff --git a/services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
similarity index 85%
rename from services/backup/java/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java
index e3df3c1..f67f100 100644
--- a/services/backup/java/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/services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
rename to packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
new file mode 100644
index 0000000..77cfded
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.LockScreenRequiredException;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+/**
+ * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new
+ * key is synced.
+ */
+public class StartSecondaryKeyRotationTask {
+    private static final String TAG = "BE-StSecondaryKeyRotTsk";
+
+    private final CryptoSettings mCryptoSettings;
+    private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+
+    public StartSecondaryKeyRotationTask(
+            CryptoSettings cryptoSettings,
+            RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) {
+        mCryptoSettings = Preconditions.checkNotNull(cryptoSettings);
+        mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager);
+    }
+
+    /** Begin the key rotation */
+    public void run() {
+        Slog.i(TAG, "Attempting to initiate a secondary key rotation.");
+
+        Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias();
+        if (!maybeCurrentAlias.isPresent()) {
+            Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation.");
+            return;
+        }
+        String currentAlias = maybeCurrentAlias.get();
+
+        Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias();
+        if (maybeNextAlias.isPresent()) {
+            String nextAlias = maybeNextAlias.get();
+            if (nextAlias.equals(currentAlias)) {
+                // Shouldn't be possible, but guard against accidentally deleting the active key.
+                Slog.e(TAG, "Was already trying to rotate to what is already the active key.");
+            } else {
+                Slog.w(TAG, "Was already rotating to another key. Cancelling that.");
+                try {
+                    mSecondaryKeyManager.remove(nextAlias);
+                } catch (Exception e) {
+                    Slog.wtf(TAG, "Could not remove old key", e);
+                }
+            }
+            mCryptoSettings.removeNextSecondaryKeyAlias();
+        }
+
+        RecoverableKeyStoreSecondaryKey newSecondaryKey;
+        try {
+            newSecondaryKey = mSecondaryKeyManager.generate();
+        } catch (LockScreenRequiredException e) {
+            Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e);
+            return;
+        } catch (InternalRecoveryServiceException e) {
+            Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e);
+            return;
+        } catch (UnrecoverableKeyException e) {
+            Slog.e(TAG, "Failed to get key after generating, failed to rotate", e);
+            return;
+        }
+
+        String alias = newSecondaryKey.getAlias();
+        Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'.");
+        try {
+            mCryptoSettings.setNextSecondaryAlias(alias);
+            Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to");
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "Unexpected error setting next alias", e);
+            try {
+                mSecondaryKeyManager.remove(alias);
+            } catch (Exception err) {
+                Slog.wtf(TAG, "Failed to remove generated key after encountering error", err);
+            }
+        }
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp
new file mode 100644
index 0000000..4e42ce7
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_robolectric_test {
+    name: "BackupEncryptionRoboTests",
+    srcs: [
+        "src/**/*.java",
+        ":FrameworksServicesRoboShadows",
+    ],
+    java_resource_dirs: ["config"],
+    libs: [
+        "backup-encryption-protos",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.rules",
+    ],
+    instrumentation_for: "BackupEncryption",
+}
+
+filegroup {
+    name: "BackupEncryptionRoboShadows",
+    srcs: ["src/com/android/server/testing/shadows/**/*.java"],
+}
diff --git a/packages/BackupEncryption/test/robolectric/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric/AndroidManifest.xml
new file mode 100644
index 0000000..ae5cdd9
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          coreApp="true"
+          package="com.android.server.backup.encryption.robotests">
+
+    <application/>
+
+</manifest>
diff --git a/packages/BackupEncryption/test/robolectric/config/robolectric.properties b/packages/BackupEncryption/test/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..26fceb3
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/config/robolectric.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+sdk=NEWEST_SDK
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java
new file mode 100644
index 0000000..979b3d5
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.Application;
+import android.security.keystore.recovery.RecoveryController;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Optional;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowRecoveryController.class)
+public class CryptoSettingsTest {
+
+    private static final String TEST_KEY_ALIAS =
+            "com.android.server.backup.encryption/keystore/08120c326b928ff34c73b9c58581da63";
+
+    private CryptoSettings mCryptoSettings;
+    private Application mApplication;
+
+    @Before
+    public void setUp() {
+        ShadowRecoveryController.reset();
+
+        mApplication = ApplicationProvider.getApplicationContext();
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication);
+    }
+
+    @Test
+    public void getActiveSecondaryAlias_isInitiallyAbsent() {
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getActiveSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void getNextSecondaryAlias_isInitiallyAbsent() {
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getNextSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void isInitialized_isInitiallyFalse() {
+        assertThat(mCryptoSettings.getIsInitialized()).isFalse();
+    }
+
+    @Test
+    public void setActiveSecondaryAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void setNextSecondaryAlias_inRecoveryController_setsAlias() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void setNextSecondaryAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void removeNextSecondaryAlias_removesIt() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS);
+
+        mCryptoSettings.removeNextSecondaryKeyAlias();
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+    }
+
+    @Test
+    public void initializeWithKeyAlias_setsAsInitialized() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getIsInitialized()).isTrue();
+    }
+
+    @Test
+    public void initializeWithKeyAlias_setsActiveAlias() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS);
+    }
+
+    @Test
+    public void initializeWithKeyAlias_throwsIfKeyIsNotInRecoveryController() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void initializeWithKeyAlias_throwsIfAlreadyInitialized() throws Exception {
+        setAliasIsInRecoveryController(TEST_KEY_ALIAS);
+        mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS);
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS));
+    }
+
+    @Test
+    public void getSecondaryLastRotated_returnsEmptyInitially() {
+        assertThat(mCryptoSettings.getSecondaryLastRotated()).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void getSecondaryLastRotated_returnsTimestampAfterItIsSet() {
+        long timestamp = 1000001;
+
+        mCryptoSettings.setSecondaryLastRotated(timestamp);
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(timestamp);
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_notSet_returnsOptionalAbsent() {
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().isPresent()).isFalse();
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_isSet_returnsSetValue() {
+        String secondaryKeyVersion = "some_secondary_key";
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion);
+
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get())
+                .isEqualTo(secondaryKeyVersion);
+    }
+
+    @Test
+    public void getAncestralSecondaryKeyVersion_isSetMultipleTimes_returnsLastSetValue() {
+        String secondaryKeyVersion1 = "some_secondary_key";
+        String secondaryKeyVersion2 = "another_secondary_key";
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion1);
+        mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion2);
+
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get())
+                .isEqualTo(secondaryKeyVersion2);
+    }
+
+    @Test
+    public void clearAllSettingsForBackup_clearsStateForBackup() throws Exception {
+        String key1 = "key1";
+        String key2 = "key2";
+        String ancestralKey = "ancestral_key";
+        setAliasIsInRecoveryController(key1);
+        setAliasIsInRecoveryController(key2);
+        mCryptoSettings.setActiveSecondaryKeyAlias(key1);
+        mCryptoSettings.setNextSecondaryAlias(key2);
+        mCryptoSettings.setSecondaryLastRotated(100001);
+        mCryptoSettings.setAncestralSecondaryKeyVersion(ancestralKey);
+
+        mCryptoSettings.clearAllSettingsForBackup();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse();
+        assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()).isEqualTo(ancestralKey);
+    }
+
+    private void setAliasIsInRecoveryController(String alias) throws Exception {
+        RecoveryController recoveryController = RecoveryController.getInstance(mApplication);
+        recoveryController.generateKey(alias);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
similarity index 98%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
index 3f57240..c12464c 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunk;
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
new file mode 100644
index 0000000..c5f78c2
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.chunk;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+
+import com.google.common.base.Charsets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ChunkListingMapTest {
+    private static final ChunkHash CHUNK_A_HASH = getHash("CHUNK_A");
+    private static final ChunkHash CHUNK_B_HASH = getHash("CHUNK_B");
+    private static final ChunkHash CHUNK_C_HASH = getHash("CHUNK_C");
+
+    private static final int CHUNK_A_LENGTH = 256;
+    private static final int CHUNK_B_LENGTH = 1024;
+    private static final int CHUNK_C_LENGTH = 4055;
+
+    private static final int CHUNK_A_START = 0;
+    private static final int CHUNK_B_START = CHUNK_A_START + CHUNK_A_LENGTH;
+    private static final int CHUNK_C_START = CHUNK_B_START + CHUNK_B_LENGTH;
+
+    private ChunkListingMap mChunkListingMap;
+
+    @Before
+    public void setUp() {
+        mChunkListingMap = createFromFixture();
+    }
+
+    @Test
+    public void hasChunk_isTrueForExistingChunks() {
+        assertThat(mChunkListingMap.hasChunk(CHUNK_A_HASH)).isTrue();
+        assertThat(mChunkListingMap.hasChunk(CHUNK_B_HASH)).isTrue();
+        assertThat(mChunkListingMap.hasChunk(CHUNK_C_HASH)).isTrue();
+    }
+
+    @Test
+    public void hasChunk_isFalseForNonexistentChunks() {
+        assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse();
+        assertThat(mChunkListingMap.hasChunk(getHash(""))).isFalse();
+    }
+
+    @Test
+    public void getChunkListing_hasCorrectLengths() {
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getLength())
+                .isEqualTo(CHUNK_A_LENGTH);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getLength())
+                .isEqualTo(CHUNK_B_LENGTH);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getLength())
+                .isEqualTo(CHUNK_C_LENGTH);
+    }
+
+    @Test
+    public void getChunkListing_hasCorrectStarts() {
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getStart())
+                .isEqualTo(CHUNK_A_START);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getStart())
+                .isEqualTo(CHUNK_B_START);
+        assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getStart())
+                .isEqualTo(CHUNK_C_START);
+    }
+
+    @Test
+    public void getChunkListing_isNullForNonExistentChunks() {
+        assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).isNull();
+    }
+
+    private static ChunkListingMap createFromFixture() {
+        ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
+        chunkListing.chunks = new ChunksMetadataProto.Chunk[3];
+        chunkListing.chunks[0] = newChunk(CHUNK_A_HASH.getHash(), CHUNK_A_LENGTH);
+        chunkListing.chunks[1] = newChunk(CHUNK_B_HASH.getHash(), CHUNK_B_LENGTH);
+        chunkListing.chunks[2] = newChunk(CHUNK_C_HASH.getHash(), CHUNK_C_LENGTH);
+        return ChunkListingMap.fromProto(chunkListing);
+    }
+
+    private static ChunkHash getHash(String name) {
+        return new ChunkHash(
+                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
+    }
+
+    public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) {
+        ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk();
+        newChunk.hash = Arrays.copyOf(hash, hash.length);
+        newChunk.length = length;
+        return newChunk;
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
similarity index 98%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
index 0bf1417..c6b29b7 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunk;
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
similarity index 86%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
index d0e5fb3..19e3b28 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
@@ -78,22 +78,22 @@
 
         // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes().
         doAnswer(
-                        new Answer<Void>() {
-                            private int mInvocation = 0;
+                    new Answer<Void>() {
+                        private int mInvocation = 0;
 
-                            @Override
-                            public Void answer(InvocationOnMock invocation) {
-                                byte[] nonceDestination = invocation.getArgument(0);
-                                System.arraycopy(
-                                        NONCES[this.mInvocation],
-                                        0,
-                                        nonceDestination,
-                                        0,
-                                        GCM_NONCE_LENGTH_BYTES);
-                                this.mInvocation++;
-                                return null;
-                            }
-                        })
+                        @Override
+                        public Void answer(InvocationOnMock invocation) {
+                            byte[] nonceDestination = invocation.getArgument(0);
+                            System.arraycopy(
+                                    NONCES[this.mInvocation],
+                                    0,
+                                    nonceDestination,
+                                    0,
+                                    GCM_NONCE_LENGTH_BYTES);
+                            this.mInvocation++;
+                            return null;
+                        }
+                    })
                 .when(mSecureRandomMock)
                 .nextBytes(any(byte[].class));
     }
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
similarity index 98%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
index 2bbbf28..72a927d 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
similarity index 98%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
index 8e801a1..325b601 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
similarity index 96%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
index 2f872be..7e1fded 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
@@ -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/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
similarity index 95%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
index 978bddb..6f58ee1 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
@@ -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/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
similarity index 98%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
index 19ef8fb..966d3e2 100644
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.encryption.chunking;
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
new file mode 100644
index 0000000..b607404
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+
+import javax.crypto.SecretKey;
+
+/** Key wrapping tests */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyWrapUtilsTest {
+    private static final int KEY_SIZE_BITS = 256;
+    private static final int BITS_PER_BYTE = 8;
+    private static final int GCM_NONCE_LENGTH_BYTES = 16;
+    private static final int GCM_TAG_LENGTH_BYTES = 16;
+
+    /** Test a wrapped key has metadata */
+    @Test
+    public void wrap_addsMetadata() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.metadata).isNotNull();
+        assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM);
+    }
+
+    /** Test a wrapped key has an algorithm specified */
+    @Test
+    public void wrap_addsWrapAlgorithm() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM);
+    }
+
+    /** Test a wrapped key haas an nonce of the right length */
+    @Test
+    public void wrap_addsNonceOfAppropriateLength() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES);
+    }
+
+    /** Test a wrapped key has a key of the right length */
+    @Test
+    public void wrap_addsTagOfAppropriateLength() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+        assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES);
+    }
+
+    /** Ensure a key can be wrapped and unwrapped again */
+    @Test
+    public void unwrap_unwrapsEncryptedKey() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        SecretKey tertiaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey);
+        SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure the unwrap method rejects keys with bad algorithms */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadWrapAlgorithm() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadKeyAlgorithm() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure the unwrap method rejects wrapped keys missing the metadata */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForMissingMetadata() throws Exception {
+        SecretKey secondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey());
+        wrappedKey.metadata = null;
+
+        KeyWrapUtils.unwrap(secondaryKey, wrappedKey);
+    }
+
+    /** Ensure unwrap rejects invalid secondary keys */
+    @Test(expected = InvalidKeyException.class)
+    public void unwrap_throwsForBadSecondaryKey() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedKey =
+                KeyWrapUtils.wrap(
+                        /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey());
+
+        KeyWrapUtils.unwrap(generateAesKey(), wrappedKey);
+    }
+
+    /** Ensure rewrap can rewrap keys */
+    @Test
+    public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception {
+        SecretKey tertiaryKey = generateAesKey();
+        SecretKey oldSecondaryKey = generateAesKey();
+        SecretKey newSecondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedWithNew =
+                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+        assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure rewrap doesn't create something decryptable by an old key */
+    @Test(expected = InvalidKeyException.class)
+    public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception {
+        SecretKey tertiaryKey = generateAesKey();
+        SecretKey oldSecondaryKey = generateAesKey();
+        SecretKey newSecondaryKey = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey);
+
+        WrappedKeyProto.WrappedKey wrappedWithNew =
+                KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld);
+
+        KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java
new file mode 100644
index 0000000..004f809
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.InvalidKeyException;
+import java.security.KeyException;
+import java.security.SecureRandom;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+/** Test the restore key fetcher */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class RestoreKeyFetcherTest {
+
+    private static final String KEY_GENERATOR_ALGORITHM = "AES";
+
+    private static final String TEST_SECONDARY_KEY_ALIAS = "test_2ndary_key";
+    private static final byte[] TEST_SECONDARY_KEY_BYTES = new byte[256 / Byte.SIZE];
+
+    @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+
+    /** Initialise the mocks **/
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    /** Ensure the unwrap method works as expected */
+    @Test
+    public void unwrapTertiaryKey_returnsUnwrappedKey() throws Exception {
+        RecoverableKeyStoreSecondaryKey secondaryKey = createSecondaryKey();
+        SecretKey tertiaryKey = createTertiaryKey();
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(secondaryKey.getSecretKey(), tertiaryKey);
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS))
+                .thenReturn(Optional.of(secondaryKey));
+
+        SecretKey actualTertiaryKey =
+                RestoreKeyFetcher.unwrapTertiaryKey(
+                        () -> mSecondaryKeyManager,
+                        TEST_SECONDARY_KEY_ALIAS,
+                        wrappedTertiaryKey);
+
+        assertThat(actualTertiaryKey).isEqualTo(tertiaryKey);
+    }
+
+    /** Ensure that missing secondary keys are detected and an appropriate exception is thrown */
+    @Test
+    public void unwrapTertiaryKey_missingSecondaryKey_throwsSpecificException() throws Exception {
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey());
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS)).thenReturn(Optional.empty());
+
+        assertThrows(
+                KeyException.class,
+                () ->
+                        RestoreKeyFetcher.unwrapTertiaryKey(
+                                () -> mSecondaryKeyManager,
+                                TEST_SECONDARY_KEY_ALIAS,
+                                wrappedTertiaryKey));
+    }
+
+    /** Ensure that invalid secondary keys are detected and an appropriate exception is thrown */
+    @Test
+    public void unwrapTertiaryKey_badSecondaryKey_throws() throws Exception {
+        RecoverableKeyStoreSecondaryKey badSecondaryKey =
+                new RecoverableKeyStoreSecondaryKey(
+                        TEST_SECONDARY_KEY_ALIAS,
+                        new SecretKeySpec(new byte[] {0, 1}, KEY_GENERATOR_ALGORITHM));
+
+        WrappedKeyProto.WrappedKey wrappedTertiaryKey =
+                KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey());
+        when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS))
+                .thenReturn(Optional.of(badSecondaryKey));
+
+        assertThrows(
+                InvalidKeyException.class,
+                () ->
+                        RestoreKeyFetcher.unwrapTertiaryKey(
+                                () -> mSecondaryKeyManager,
+                                TEST_SECONDARY_KEY_ALIAS,
+                                wrappedTertiaryKey));
+    }
+
+    private static RecoverableKeyStoreSecondaryKey createSecondaryKey() {
+        return new RecoverableKeyStoreSecondaryKey(
+                TEST_SECONDARY_KEY_ALIAS,
+                new SecretKeySpec(TEST_SECONDARY_KEY_BYTES, KEY_GENERATOR_ALGORITHM));
+    }
+
+    private static SecretKey createTertiaryKey() {
+        return new TertiaryKeyGenerator(new SecureRandom(new byte[] {0})).generate();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
new file mode 100644
index 0000000..c31d19d
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+import java.io.File;
+import java.time.Clock;
+
+@Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class)
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class SecondaryKeyRotationSchedulerTest {
+    private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation";
+
+    @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    @Mock private Clock mClock;
+
+    private CryptoSettings mCryptoSettings;
+    private SecondaryKeyRotationScheduler mScheduler;
+    private long mRotationIntervalMillis;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context application = ApplicationProvider.getApplicationContext();
+
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(application);
+        mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs();
+
+        mScheduler =
+                new SecondaryKeyRotationScheduler(
+                        application, mSecondaryKeyManager, mCryptoSettings, mClock);
+        ShadowStartSecondaryKeyRotationTask.reset();
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() {
+        long lastRotated = 100009;
+        long now = lastRotated + mRotationIntervalMillis;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfClockHasChanged() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception {
+        File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH);
+        file.createNewFile();
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue();
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() {
+        long lastRotated = 100009;
+        long now = lastRotated - 1;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    @Test
+    public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse();
+    }
+
+    @Test
+    public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() {
+        long lastRotated = 100009;
+        mCryptoSettings.setSecondaryLastRotated(lastRotated);
+        setNow(lastRotated + mRotationIntervalMillis - 1);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated);
+    }
+
+    @Test
+    public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() {
+        long now = 13295436;
+        setNow(now);
+
+        mScheduler.startRotationIfScheduled();
+
+        assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now);
+    }
+
+    private void setNow(long timestamp) {
+        when(mClock.millis()).thenReturn(timestamp);
+    }
+
+    @Implements(StartSecondaryKeyRotationTask.class)
+    public static class ShadowStartSecondaryKeyRotationTask {
+        private static boolean sRan = false;
+
+        @Implementation
+        public void run() {
+            sRan = true;
+        }
+
+        @Resetter
+        public static void reset() {
+            sRan = false;
+        }
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java
new file mode 100644
index 0000000..1ed8309
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.security.SecureRandom;
+
+import javax.crypto.SecretKey;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowRecoveryController.class)
+public class TertiaryKeyManagerTest {
+
+    private static final String TEST_PACKAGE_1 = "com.example.app1";
+    private static final String TEST_PACKAGE_2 = "com.example.app2";
+
+    private SecureRandom mSecureRandom;
+    private RecoverableKeyStoreSecondaryKey mSecondaryKey;
+
+    @Mock private TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mSecureRandom = new SecureRandom();
+        mSecondaryKey =
+                new RecoverableKeyStoreSecondaryKeyManager(
+                                RecoveryController.getInstance(application), mSecureRandom)
+                        .generate();
+        ShadowRecoveryController.reset();
+    }
+
+    private TertiaryKeyManager createNewManager(String packageName) {
+        return new TertiaryKeyManager(
+                application,
+                mSecureRandom,
+                mTertiaryKeyRotationScheduler,
+                mSecondaryKey,
+                packageName);
+    }
+
+    @Test
+    public void getKey_noExistingKey_returnsNewKey() throws Exception {
+        assertThat(createNewManager(TEST_PACKAGE_1).getKey()).isNotNull();
+    }
+
+    @Test
+    public void getKey_noExistingKey_recordsIncrementalBackup() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+    }
+
+    @Test
+    public void getKey_existingKey_returnsExistingKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        SecretKey existingKey = manager.getKey();
+
+        assertThat(manager.getKey()).isEqualTo(existingKey);
+    }
+
+    @Test
+    public void getKey_existingKey_recordsBackupButNotRotation() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        reset(mTertiaryKeyRotationScheduler);
+
+        createNewManager(TEST_PACKAGE_1).getKey();
+
+        verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+        verify(mTertiaryKeyRotationScheduler, never()).recordKeyRotation(any());
+    }
+
+    @Test
+    public void getKey_existingKeyButRotationRequired_returnsNewKey() throws Exception {
+        SecretKey firstKey = createNewManager(TEST_PACKAGE_1).getKey();
+        when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true);
+
+        SecretKey secondKey = createNewManager(TEST_PACKAGE_1).getKey();
+
+        assertThat(secondKey).isNotEqualTo(firstKey);
+    }
+
+    @Test
+    public void getKey_existingKeyButRotationRequired_recordsKeyRotationAndBackup()
+            throws Exception {
+        when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true);
+        createNewManager(TEST_PACKAGE_1).getKey();
+
+        InOrder inOrder = inOrder(mTertiaryKeyRotationScheduler);
+        inOrder.verify(mTertiaryKeyRotationScheduler).recordKeyRotation(TEST_PACKAGE_1);
+        inOrder.verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1);
+    }
+
+    @Test
+    public void getKey_twoApps_returnsDifferentKeys() throws Exception {
+        TertiaryKeyManager firstManager = createNewManager(TEST_PACKAGE_1);
+        TertiaryKeyManager secondManager = createNewManager(TEST_PACKAGE_2);
+        SecretKey firstKey = firstManager.getKey();
+
+        assertThat(secondManager.getKey()).isNotEqualTo(firstKey);
+    }
+
+    @Test
+    public void getWrappedKey_noExistingKey_returnsWrappedNewKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        SecretKey unwrappedKey = manager.getKey();
+        WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey();
+
+        SecretKey expectedUnwrappedKey =
+                KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey);
+    }
+
+    @Test
+    public void getWrappedKey_existingKey_returnsWrappedExistingKey() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey();
+        SecretKey unwrappedKey = manager.getKey();
+
+        SecretKey expectedUnwrappedKey =
+                KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey);
+        assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey);
+    }
+
+    @Test
+    public void wasKeyRotated_noExistingKey_returnsTrue() throws Exception {
+        TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1);
+        assertThat(manager.wasKeyRotated()).isTrue();
+    }
+
+    @Test
+    public void wasKeyRotated_existingKey_returnsFalse() throws Exception {
+        createNewManager(TEST_PACKAGE_1).getKey();
+        assertThat(createNewManager(TEST_PACKAGE_1).wasKeyRotated()).isFalse();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java
new file mode 100644
index 0000000..dfc7e2b
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.content.Context;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.File;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for the tertiary key rotation scheduler */
+@RunWith(RobolectricTestRunner.class)
+public final class TertiaryKeyRotationSchedulerTest {
+
+    private static final int MAXIMUM_ROTATIONS_PER_WINDOW = 2;
+    private static final int MAX_BACKUPS_TILL_ROTATION = 31;
+    private static final String SHARED_PREFS_NAME = "tertiary_key_rotation_tracker";
+    private static final String PACKAGE_1 = "com.android.example1";
+    private static final String PACKAGE_2 = "com.android.example2";
+
+    @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @Mock private Clock mClock;
+
+    private File mFile;
+    private TertiaryKeyRotationScheduler mScheduler;
+
+    /** Setup the scheduler for test */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mFile = temporaryFolder.newFile();
+        mScheduler =
+                new TertiaryKeyRotationScheduler(
+                        new TertiaryKeyRotationTracker(
+                                application.getSharedPreferences(
+                                        SHARED_PREFS_NAME, Context.MODE_PRIVATE),
+                                MAX_BACKUPS_TILL_ROTATION),
+                        new TertiaryKeyRotationWindowedCount(mFile, mClock),
+                        MAXIMUM_ROTATIONS_PER_WINDOW);
+    }
+
+    /** Test we don't trigger a rotation straight off */
+    @Test
+    public void isKeyRotationDue_isFalseInitially() {
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test we don't prematurely trigger a rotation */
+    @Test
+    public void isKeyRotationDue_isFalseAfterInsufficientBackups() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION - 1);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test we do trigger a backup */
+    @Test
+    public void isKeyRotationDue_isTrueAfterEnoughBackups() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test rotation will occur if the quota allows */
+    @Test
+    public void isKeyRotationDue_isTrueIfRotationQuotaRemainsInWindow() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test rotation is blocked if the quota has been exhausted */
+    @Test
+    public void isKeyRotationDue_isFalseIfEnoughRotationsHaveHappenedInWindow() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test rotation is due after one window has passed */
+    @Test
+    public void isKeyRotationDue_isTrueAfterAWholeWindowHasPassed() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        mScheduler.recordKeyRotation(PACKAGE_2);
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue();
+    }
+
+    /** Test the rotation state changes after a rotation */
+    @Test
+    public void isKeyRotationDue_isFalseAfterRotation() {
+        simulateBackups(MAX_BACKUPS_TILL_ROTATION);
+        mScheduler.recordKeyRotation(PACKAGE_1);
+        assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse();
+    }
+
+    /** Test the rate limiting for a given window */
+    @Test
+    public void isKeyRotationDue_neverAllowsMoreThanInWindow() {
+        List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION);
+
+        // simulate backups of all apps each night
+        for (int i = 0; i < 300; i++) {
+            setTimeMillis(i * TimeUnit.HOURS.toMillis(24));
+            int rotationsThisNight = 0;
+            for (String app : apps) {
+                if (mScheduler.isKeyRotationDue(app)) {
+                    rotationsThisNight++;
+                    mScheduler.recordKeyRotation(app);
+                } else {
+                    mScheduler.recordBackup(app);
+                }
+            }
+            assertThat(rotationsThisNight).isAtMost(MAXIMUM_ROTATIONS_PER_WINDOW);
+        }
+    }
+
+    /** Test that backups are staggered over the window */
+    @Test
+    public void isKeyRotationDue_naturallyStaggersBackupsOverTime() {
+        List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION);
+
+        HashMap<String, ArrayList<Integer>> rotationDays = new HashMap<>();
+        for (String app : apps) {
+            rotationDays.put(app, new ArrayList<>());
+        }
+
+        // simulate backups of all apps each night
+        for (int i = 0; i < 300; i++) {
+            setTimeMillis(i * TimeUnit.HOURS.toMillis(24));
+            for (String app : apps) {
+                if (mScheduler.isKeyRotationDue(app)) {
+                    rotationDays.get(app).add(i);
+                    mScheduler.recordKeyRotation(app);
+                } else {
+                    mScheduler.recordBackup(app);
+                }
+            }
+        }
+
+        for (String app : apps) {
+            List<Integer> days = rotationDays.get(app);
+            for (int i = 1; i < days.size(); i++) {
+                assertThat(days.get(i) - days.get(i - 1)).isEqualTo(MAX_BACKUPS_TILL_ROTATION + 1);
+            }
+        }
+    }
+
+    private ArrayList<String> makeTestApps(int n) {
+        ArrayList<String> apps = new ArrayList<>();
+        for (int i = 0; i < n; i++) {
+            apps.add(String.format(Locale.US, "com.android.app%d", i));
+        }
+        return apps;
+    }
+
+    private void simulateBackups(int numberOfBackups) {
+        while (numberOfBackups > 0) {
+            mScheduler.recordBackup(PACKAGE_1);
+            numberOfBackups--;
+        }
+    }
+
+    private void setTimeMillis(long timeMillis) {
+        when(mClock.millis()).thenReturn(timeMillis);
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java
new file mode 100644
index 0000000..bd30977
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.File;
+import java.io.IOException;
+import java.time.Clock;
+import java.util.concurrent.TimeUnit;
+
+/** Tests for {@link TertiaryKeyRotationWindowedCount}. */
+@RunWith(RobolectricTestRunner.class)
+public class TertiaryKeyRotationWindowedCountTest {
+    private static final int TIMESTAMP_SIZE_IN_BYTES = 8;
+
+    @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    @Mock private Clock mClock;
+
+    private File mFile;
+    private TertiaryKeyRotationWindowedCount mWindowedcount;
+
+    /** Setup the windowed counter for testing */
+    @Before
+    public void setUp() throws IOException {
+        MockitoAnnotations.initMocks(this);
+        mFile = mTemporaryFolder.newFile();
+        mWindowedcount = new TertiaryKeyRotationWindowedCount(mFile, mClock);
+    }
+
+    /** Test handling bad files */
+    @Test
+    public void constructor_doesNotFailForBadFile() throws IOException {
+        new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock);
+    }
+
+    /** Test the count is 0 to start */
+    @Test
+    public void getCount_isZeroInitially() {
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Test the count is correct for a time window */
+    @Test
+    public void getCount_includesResultsInLastTwentyFourHours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(4));
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(23));
+        mWindowedcount.record();
+        mWindowedcount.record();
+        assertThat(mWindowedcount.getCount()).isEqualTo(4);
+    }
+
+    /** Test old results are ignored */
+    @Test
+    public void getCount_ignoresResultsOlderThanTwentyFourHours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Test future events are removed if the clock moves backways (e.g. DST, TZ change) */
+    @Test
+    public void getCount_removesFutureEventsIfClockHasChanged() {
+        setTimeMillis(1000);
+        mWindowedcount.record();
+        setTimeMillis(0);
+        assertThat(mWindowedcount.getCount()).isEqualTo(0);
+    }
+
+    /** Check recording doesn't fail for a bad file */
+    @Test
+    public void record_doesNotFailForBadFile() throws Exception {
+        new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock).record();
+    }
+
+    /** Checks the state is persisted */
+    @Test
+    public void record_persistsStateToDisk() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        assertThat(new TertiaryKeyRotationWindowedCount(mFile, mClock).getCount()).isEqualTo(1);
+    }
+
+    /** Test the file doesn't contain unnecessary data */
+    @Test
+    public void record_compactsFileToLast24Hours() {
+        setTimeMillis(0);
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(TIMESTAMP_SIZE_IN_BYTES);
+        setTimeMillis(1);
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES);
+        setTimeMillis(TimeUnit.HOURS.toMillis(24));
+        mWindowedcount.record();
+        assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES);
+    }
+
+    private void setTimeMillis(long timeMillis) {
+        when(mClock.millis()).thenReturn(timeMillis);
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java
new file mode 100644
index 0000000..ccc5f32
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.keys;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.assertTrue;
+
+import android.content.Context;
+
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.security.InvalidKeyException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+
+/** Tests for the tertiary key store */
+@RunWith(RobolectricTestRunner.class)
+public class TertiaryKeyStoreTest {
+
+    private static final String SECONDARY_KEY_ALIAS = "Robbo/Ranx";
+
+    private Context mApplication;
+    private TertiaryKeyStore mTertiaryKeyStore;
+    private SecretKey mSecretKey;
+
+    /** Initialise the keystore for testing */
+    @Before
+    public void setUp() throws Exception {
+        mApplication = RuntimeEnvironment.application;
+        mSecretKey = generateAesKey();
+        mTertiaryKeyStore =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, mSecretKey));
+    }
+
+    /** Test a reound trip for a key */
+    @Test
+    public void load_loadsAKeyThatWasSaved() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+
+        Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
+
+        assertTrue(maybeLoadedKey.isPresent());
+        assertEquals(packageKey, maybeLoadedKey.get());
+    }
+
+    /** Test isolation between packages */
+    @Test
+    public void load_doesNotLoadAKeyForAnotherSecondary() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+        TertiaryKeyStore managerWithOtherSecondaryKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(
+                                "myNewSecondaryKeyAlias", generateAesKey()));
+
+        assertFalse(managerWithOtherSecondaryKey.load(packageName).isPresent());
+    }
+
+    /** Test non-existent key handling */
+    @Test
+    public void load_returnsAbsentForANonExistentKey() throws Exception {
+        assertFalse(mTertiaryKeyStore.load("mystery.package").isPresent());
+    }
+
+    /** Test handling incorrect keys */
+    @Test
+    public void load_throwsIfHasWrongBackupKey() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey packageKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, packageKey);
+        TertiaryKeyStore managerWithBadKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, generateAesKey()));
+
+        assertThrows(InvalidKeyException.class, () -> managerWithBadKey.load(packageName));
+    }
+
+    /** Test handling of empty app name */
+    @Test
+    public void load_throwsForEmptyApplicationName() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> mTertiaryKeyStore.load(""));
+    }
+
+    /** Test handling of an invalid app name */
+    @Test
+    public void load_throwsForBadApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mTertiaryKeyStore.load("com/android/example"));
+    }
+
+    /** Test key replacement */
+    @Test
+    public void save_overwritesPreviousKey() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey oldKey = generateAesKey();
+        mTertiaryKeyStore.save(packageName, oldKey);
+        SecretKey newKey = generateAesKey();
+
+        mTertiaryKeyStore.save(packageName, newKey);
+
+        Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName);
+        assertTrue(maybeLoadedKey.isPresent());
+        SecretKey loadedKey = maybeLoadedKey.get();
+        assertThat(loadedKey).isNotEqualTo(oldKey);
+        assertThat(loadedKey).isEqualTo(newKey);
+    }
+
+    /** Test saving with an empty application name fails */
+    @Test
+    public void save_throwsForEmptyApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class, () -> mTertiaryKeyStore.save("", generateAesKey()));
+    }
+
+    /** Test saving an invalid application name fails */
+    @Test
+    public void save_throwsForBadApplicationName() throws Exception {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> mTertiaryKeyStore.save("com/android/example", generateAesKey()));
+    }
+
+    /** Test handling an empty database */
+    @Test
+    public void getAll_returnsEmptyMapForEmptyDb() throws Exception {
+        assertThat(mTertiaryKeyStore.getAll()).isEmpty();
+    }
+
+    /** Test loading all available keys works as expected */
+    @Test
+    public void getAll_returnsAllKeysSaved() throws Exception {
+        String package1 = "com.android.example";
+        SecretKey key1 = generateAesKey();
+        String package2 = "com.anndroid.example1";
+        SecretKey key2 = generateAesKey();
+        String package3 = "com.android.example2";
+        SecretKey key3 = generateAesKey();
+        mTertiaryKeyStore.save(package1, key1);
+        mTertiaryKeyStore.save(package2, key2);
+        mTertiaryKeyStore.save(package3, key3);
+
+        Map<String, SecretKey> keys = mTertiaryKeyStore.getAll();
+
+        assertThat(keys).containsExactly(package1, key1, package2, key2, package3, key3);
+    }
+
+    /** Test cross-secondary isolation */
+    @Test
+    public void getAll_doesNotReturnKeysForOtherSecondary() throws Exception {
+        String packageName = "com.android.example";
+        TertiaryKeyStore managerWithOtherSecondaryKey =
+                TertiaryKeyStore.newInstance(
+                        mApplication,
+                        new RecoverableKeyStoreSecondaryKey(
+                                "myNewSecondaryKeyAlias", generateAesKey()));
+        managerWithOtherSecondaryKey.save(packageName, generateAesKey());
+
+        assertThat(mTertiaryKeyStore.getAll()).isEmpty();
+    }
+
+    /** Test mass put into the keystore */
+    @Test
+    public void putAll_putsAllWrappedKeysInTheStore() throws Exception {
+        String packageName = "com.android.example";
+        SecretKey key = generateAesKey();
+        WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(mSecretKey, key);
+
+        Map<String, WrappedKeyProto.WrappedKey> testElements = new HashMap<>();
+        testElements.put(packageName, wrappedKey);
+        mTertiaryKeyStore.putAll(testElements);
+
+        assertThat(mTertiaryKeyStore.getAll()).containsKey(packageName);
+        assertThat(mTertiaryKeyStore.getAll().get(packageName).getEncoded())
+                .isEqualTo(key.getEncoded());
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
new file mode 100644
index 0000000..215e1cb
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.os.Debug;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ChunkHasher;
+import com.android.server.backup.encryption.protos.nano.KeyValuePairProto;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class DecryptedChunkKvOutputTest {
+    private static final String TEST_KEY_1 = "key_1";
+    private static final String TEST_KEY_2 = "key_2";
+    private static final byte[] TEST_VALUE_1 = {1, 2, 3};
+    private static final byte[] TEST_VALUE_2 = {10, 11, 12, 13};
+    private static final byte[] TEST_PAIR_1 = toByteArray(createPair(TEST_KEY_1, TEST_VALUE_1));
+    private static final byte[] TEST_PAIR_2 = toByteArray(createPair(TEST_KEY_2, TEST_VALUE_2));
+    private static final int TEST_BUFFER_SIZE = Math.max(TEST_PAIR_1.length, TEST_PAIR_2.length);
+
+    @Mock private ChunkHasher mChunkHasher;
+    private DecryptedChunkKvOutput mOutput;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mChunkHasher.computeHash(any()))
+                .thenAnswer(invocation -> fakeHash(invocation.getArgument(0)));
+        mOutput = new DecryptedChunkKvOutput(mChunkHasher);
+    }
+
+    @Test
+    public void open_returnsInstance() throws Exception {
+        assertThat(mOutput.open()).isEqualTo(mOutput);
+    }
+
+    @Test
+    public void processChunk_alreadyClosed_throws() throws Exception {
+        mOutput.open();
+        mOutput.close();
+
+        assertThrows(
+                IllegalStateException.class,
+                () -> mOutput.processChunk(TEST_PAIR_1, TEST_PAIR_1.length));
+    }
+
+    @Test
+    public void getDigest_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getDigest());
+    }
+
+    @Test
+    public void getDigest_returnsDigestOfSortedHashes() throws Exception {
+        mOutput.open();
+        Debug.waitForDebugger();
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.close();
+
+        byte[] actualDigest = mOutput.getDigest();
+
+        MessageDigest digest = MessageDigest.getInstance(DecryptedChunkKvOutput.DIGEST_ALGORITHM);
+        Stream.of(TEST_PAIR_1, TEST_PAIR_2)
+                .map(DecryptedChunkKvOutputTest::fakeHash)
+                .sorted(Comparator.naturalOrder())
+                .forEachOrdered(hash -> digest.update(hash.getHash()));
+        assertThat(actualDigest).isEqualTo(digest.digest());
+    }
+
+    @Test
+    public void getPairs_beforeClose_throws() throws Exception {
+        // TODO: b/141356823 We should add a test which calls .open() here
+        assertThrows(IllegalStateException.class, () -> mOutput.getPairs());
+    }
+
+    @Test
+    public void getPairs_returnsPairsSortedByKey() throws Exception {
+        mOutput.open();
+        // Write out of order to check that it sorts the chunks.
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length);
+        mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length);
+        mOutput.close();
+
+        List<KeyValuePairProto.KeyValuePair> pairs = mOutput.getPairs();
+
+        assertThat(
+                        isInOrder(
+                                pairs,
+                                Comparator.comparing(
+                                        (KeyValuePairProto.KeyValuePair pair) -> pair.key)))
+                .isTrue();
+        assertThat(pairs).hasSize(2);
+        assertThat(pairs.get(0).key).isEqualTo(TEST_KEY_1);
+        assertThat(pairs.get(0).value).isEqualTo(TEST_VALUE_1);
+        assertThat(pairs.get(1).key).isEqualTo(TEST_KEY_2);
+        assertThat(pairs.get(1).value).isEqualTo(TEST_VALUE_2);
+    }
+
+    private static KeyValuePairProto.KeyValuePair createPair(String key, byte[] value) {
+        KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair();
+        pair.key = key;
+        pair.value = value;
+        return pair;
+    }
+
+    private boolean isInOrder(
+            List<KeyValuePairProto.KeyValuePair> list,
+            Comparator<KeyValuePairProto.KeyValuePair> comparator) {
+        if (list.size() < 2) {
+            return true;
+        }
+
+        List<KeyValuePairProto.KeyValuePair> sortedList = new ArrayList<>(list);
+        Collections.sort(sortedList, comparator);
+        return list.equals(sortedList);
+    }
+
+    private static byte[] toByteArray(KeyValuePairProto.KeyValuePair nano) {
+        return KeyValuePairProto.KeyValuePair.toByteArray(nano);
+    }
+
+    private static ChunkHash fakeHash(byte[] data) {
+        return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES));
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
new file mode 100644
index 0000000..acc6628
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.kv;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import com.google.common.collect.ImmutableMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class KeyValueListingBuilderTest {
+    private static final String TEST_KEY_1 = "test_key_1";
+    private static final String TEST_KEY_2 = "test_key_2";
+    private static final ChunkHash TEST_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1, 2}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {5, 6}, ChunkHash.HASH_LENGTH_BYTES));
+
+    private KeyValueListingBuilder mBuilder;
+
+    @Before
+    public void setUp() {
+        mBuilder = new KeyValueListingBuilder();
+    }
+
+    @Test
+    public void addPair_nullKey_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(null, TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_emptyKey_throws() {
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.addPair("", TEST_HASH_1));
+    }
+
+    @Test
+    public void addPair_nullHash_throws() {
+        assertThrows(NullPointerException.class, () -> mBuilder.addPair(TEST_KEY_1, null));
+    }
+
+    @Test
+    public void build_noPairs_buildsEmptyListing() {
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries).isEmpty();
+    }
+
+    @Test
+    public void build_returnsCorrectListing() {
+        mBuilder.addPair(TEST_KEY_1, TEST_HASH_1);
+
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(1);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+    }
+
+    @Test
+    public void addAll_addsAllPairsInMap() {
+        ImmutableMap<String, ChunkHash> pairs =
+                new ImmutableMap.Builder<String, ChunkHash>()
+                        .put(TEST_KEY_1, TEST_HASH_1)
+                        .put(TEST_KEY_2, TEST_HASH_2)
+                        .build();
+
+        mBuilder.addAll(pairs);
+        KeyValueListingProto.KeyValueListing listing = mBuilder.build();
+
+        assertThat(listing.entries.length).isEqualTo(2);
+        assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1);
+        assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash());
+        assertThat(listing.entries[1].key).isEqualTo(TEST_KEY_2);
+        assertThat(listing.entries[1].hash).isEqualTo(TEST_HASH_2.getHash());
+    }
+
+    @Test
+    public void emptyListing_returnsListingWithoutAnyPairs() {
+        KeyValueListingProto.KeyValueListing emptyListing = KeyValueListingBuilder.emptyListing();
+        assertThat(emptyListing.entries).isEmpty();
+    }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/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/services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java
similarity index 100%
rename from services/robotests/backup/src/com/android/server/backup/testing/RandomInputStream.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
similarity index 97%
rename from services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
index 0428796..3f3494d 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/CryptoTestUtils.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.backup.testing;
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
similarity index 100%
rename from services/robotests/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowRecoveryController.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java
similarity index 100%
rename from services/robotests/src/com/android/server/testing/shadows/ShadowRecoveryController.java
rename to packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java
diff --git a/packages/BackupRestoreConfirmation/res/values-in/strings.xml b/packages/BackupRestoreConfirmation/res/values-in/strings.xml
index 63a3772..5c82adf 100644
--- a/packages/BackupRestoreConfirmation/res/values-in/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-in/strings.xml
@@ -16,9 +16,9 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="backup_confirm_title" msgid="827563724209303345">"Backup sepenuhnya"</string>
+    <string name="backup_confirm_title" msgid="827563724209303345">"Pencadangan lengkap"</string>
     <string name="restore_confirm_title" msgid="5469365809567486602">"Pemulihan sepenuhnya"</string>
-    <string name="backup_confirm_text" msgid="1878021282758896593">"Backup lengkap semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?\n\nJika Anda tidak meminta backup ini, jangan izinkan operasi dilanjutkan."</string>
+    <string name="backup_confirm_text" msgid="1878021282758896593">"Pencadangan semua data ke komputer yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini dilakukan?\n\nJika Anda tidak meminta pencadangan ini, jangan izinkan operasi dilanjutkan."</string>
     <string name="allow_backup_button_label" msgid="4217228747769644068">"Cadangkan data saya"</string>
     <string name="deny_backup_button_label" msgid="6009119115581097708">"Jangan mencadangkan"</string>
     <string name="restore_confirm_text" msgid="7499866728030461776">"Pemulihan lengkap semua data dari komputer desktop yang tersambung telah diminta. Apakah Anda ingin mengizinkan hal ini?\n\nJika Anda tidak meminta pemulihan ini, jangan izinkan operasi dilanjutkan. Operasi ini akan mengganti data apa pun yang saat ini ada dalam perangkat!"</string>
@@ -31,8 +31,8 @@
     <string name="backup_enc_password_optional" msgid="1350137345907579306">"Jika Anda ingin mengenkripsi data cadangan lengkap, masukkan sandi di bawah:"</string>
     <string name="backup_enc_password_required" msgid="7889652203371654149">"Karena perangkat Anda dienkripsi, Anda perlu mengenkripsi cadangan. Masukkan sandi di bawah:"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"Jika data pemulihan dienkripsi, masukkan sandi di bawah:"</string>
-    <string name="toast_backup_started" msgid="550354281452756121">"Backup dimulai..."</string>
-    <string name="toast_backup_ended" msgid="3818080769548726424">"Backup selesai"</string>
+    <string name="toast_backup_started" msgid="550354281452756121">"Pencadangan dimulai..."</string>
+    <string name="toast_backup_ended" msgid="3818080769548726424">"Pencadangan selesai"</string>
     <string name="toast_restore_started" msgid="7881679218971277385">"Pemulihan dimulai..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"Pemulihan berakhir"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"Waktu tunggu operasi habis"</string>
diff --git a/packages/CarSystemUI/Android.bp b/packages/CarSystemUI/Android.bp
index bbae9ff..672879a 100644
--- a/packages/CarSystemUI/Android.bp
+++ b/packages/CarSystemUI/Android.bp
@@ -13,18 +13,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 //
-android_app {
-    name: "CarSystemUI",
-
-    overrides: [
-        "SystemUI",
-    ],
+android_library {
+    name: "CarSystemUI-core",
 
     srcs: [
         "src/**/*.java",
         "src/**/I*.aidl",
     ],
 
+    resource_dirs: [
+        "res-keyguard",
+        "res",
+    ],
+
     static_libs: [
         "SystemUI-core",
         "CarNotificationLib",
@@ -58,6 +59,28 @@
 
     manifest: "AndroidManifest.xml",
 
+    plugins: ["dagger2-compiler-2.19"],
+
+}
+
+android_app {
+    name: "CarSystemUI",
+
+    static_libs: [
+        "CarSystemUI-core",
+    ],
+
+    libs: [
+        "telephony-common",
+        "android.car",
+    ],
+
+    resource_dirs: [],
+
+     overrides: [
+        "SystemUI",
+    ],
+
     platform_apis: true,
     product_specific: true,
     certificate: "platform",
@@ -68,12 +91,6 @@
             "proguard.flags",
         ],
     },
-    resource_dirs: [
-        "res-keyguard",
-        "res",
-    ],
-
-
     dxflags: ["--multi-dex"],
 
     aaptflags: [
@@ -81,6 +98,8 @@
         "com.android.keyguard",
     ],
 
+    kotlincflags: ["-Xjvm-default=enable"],
+
     plugins: ["dagger2-compiler-2.19"],
 
     required: ["privapp_whitelist_com.android.systemui"],
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 728a239..e1bcc2e 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -42,6 +42,14 @@
     </com.android.systemui.statusbar.BackDropView>
 
     <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_for_bubble"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+    />
+
+    <com.android.systemui.statusbar.ScrimView
         android:id="@+id/scrim_behind"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml b/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
index 2d9901c..9df78f1 100644
--- a/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
+++ b/packages/CarSystemUI/res/layout/trust_agent_unlock_dialog.xml
@@ -16,12 +16,13 @@
   -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layout"
+    android:id="@+id/unlock_dialog_parent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:gravity="center">
 
     <LinearLayout
+        android:id="@+id/unlock_dialog"
         android:layout_width="@dimen/unlock_dialog_width"
         android:layout_height="wrap_content"
         android:gravity="center"
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index e0ae4566..5fcf38fc 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -32,7 +32,7 @@
     <color name="system_bar_background_opaque">#ff172026</color>
 
     <color name="status_bar_background_color">#33000000</color>
-    <drawable name="system_bar_background">@android:color/transparent</drawable>
+    <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
 
     <!-- The background color of the notification shade -->
     <color name="notification_shade_background_color">#D6000000</color>
diff --git a/packages/CarSystemUI/res/values/integers_car.xml b/packages/CarSystemUI/res/values/integers_car.xml
index 862ba75..fb67b30 100644
--- a/packages/CarSystemUI/res/values/integers_car.xml
+++ b/packages/CarSystemUI/res/values/integers_car.xml
@@ -32,6 +32,6 @@
     at will result in remaining closed the panel if released-->
     <integer name="notification_settle_close_percentage">80</integer>
     <!-- The delay before the unlock dialog pops up -->
-    <integer name="unlock_dialog_delay_ms">3000</integer>
+    <integer name="unlock_dialog_delay_ms">0</integer>
 
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
index d6b766b..264b7d5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIRootComponent.java
@@ -25,7 +25,6 @@
         modules = {
                 DependencyProvider.class,
                 DependencyBinder.class,
-                ServiceBinder.class,
                 SystemUIFactory.ContextHolder.class,
                 SystemUIModule.class,
                 CarSystemUIModule.class
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index ec40cc3..afd722b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -30,8 +32,10 @@
         NotificationInterruptionStateProvider {
 
     @Inject
-    public CarNotificationInterruptionStateProvider(Context context) {
-        super(context);
+    public CarNotificationInterruptionStateProvider(Context context,
+            NotificationFilter filter,
+            StatusBarStateController stateController) {
+        super(context, filter, stateController);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7ea83f5..b0ab5b4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -948,8 +948,8 @@
         pw.print("  mNavigationBarView=");
         pw.println(mNavigationBarView);
 
-        if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
-            KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
+        if (Dependency.get(KeyguardUpdateMonitor.class) != null) {
+            Dependency.get(KeyguardUpdateMonitor.class).dump(fd, pw, args);
         }
 
         Dependency.get(FalsingManager.class).dump(pw);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
index 013c63b..ec72ee7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarTrustAgentUnlockDialogHelper.java
@@ -52,7 +52,7 @@
      * Not using Dialog because context passed from {@link FullscreenUserSwitcher} is not an
      * activity.
      */
-    private final View mUnlockDialog;
+    private final View mUnlockDialogLayout;
     private final TextView mUnlockingText;
     private final Button mButton;
     private final IntentFilter mFilter;
@@ -70,14 +70,25 @@
         mParams.packageName = mContext.getPackageName();
         mParams.setTitle(mContext.getString(R.string.unlock_dialog_title));
 
-        mUnlockDialog = LayoutInflater.from(mContext).inflate(
+        mUnlockDialogLayout = LayoutInflater.from(mContext).inflate(
             R.layout.trust_agent_unlock_dialog, null);
-        mUnlockDialog.setLayoutParams(mParams);
+        mUnlockDialogLayout.setLayoutParams(mParams);
 
-        mUnlockingText = mUnlockDialog.findViewById(R.id.unlocking_text);
-        mButton = mUnlockDialog.findViewById(R.id.enter_pin_button);
+        View dialogParent = mUnlockDialogLayout.findViewById(R.id.unlock_dialog_parent);
+        dialogParent.setOnTouchListener((v, event)-> {
+            hideUnlockDialog(/* dismissUserSwitcher= */ false);
+            return true;
+        });
+        View unlockDialog = mUnlockDialogLayout.findViewById(R.id.unlock_dialog);
+        unlockDialog.setOnTouchListener((v, event) -> {
+            // If the person taps inside the unlock dialog, the touch event will be intercepted here
+            // and the dialog will not exit
+            return true;
+        });
+        mUnlockingText = mUnlockDialogLayout.findViewById(R.id.unlocking_text);
+        mButton = mUnlockDialogLayout.findViewById(R.id.enter_pin_button);
         mButton.setOnClickListener(v -> {
-            hideUnlockDialog(/* notifyOnHideListener= */true);
+            hideUnlockDialog(/* dismissUserSwitcher= */true);
             // TODO(b/138250105) Stop unlock advertising
         });
 
@@ -133,7 +144,7 @@
                 if (!mUserManager.isUserUnlocked(uid)) {
                     logd("Showed unlock dialog for user: " + uid + " after " + delayMillis
                             + " delay.");
-                    mWindowManager.addView(mUnlockDialog, mParams);
+                    mWindowManager.addView(mUnlockDialogLayout, mParams);
                 }
             }, delayMillis);
         }
@@ -142,22 +153,22 @@
 
     private void setUid(int uid) {
         mUid = uid;
-        TextView userName = mUnlockDialog.findViewById(R.id.user_name);
+        TextView userName = mUnlockDialogLayout.findViewById(R.id.user_name);
         userName.setText(mUserManager.getUserInfo(mUid).name);
-        ImageView avatar = mUnlockDialog.findViewById(R.id.avatar);
+        ImageView avatar = mUnlockDialogLayout.findViewById(R.id.avatar);
         avatar.setImageBitmap(mUserManager.getUserIcon(mUid));
         setButtonText();
     }
 
-    private void hideUnlockDialog(boolean notifyOnHideListener) {
+    private void hideUnlockDialog(boolean dismissUserSwitcher) {
         if (!mIsDialogShowing) {
             return;
         }
-        mWindowManager.removeView(mUnlockDialog);
+        mWindowManager.removeView(mUnlockDialogLayout);
         logd("Receiver unregistered");
         mContext.unregisterReceiver(this);
-        if (notifyOnHideListener && mOnHideListener != null) {
-            mOnHideListener.onHide();
+        if (mOnHideListener != null) {
+            mOnHideListener.onHide(dismissUserSwitcher);
         }
         mIsDialogShowing = false;
     }
@@ -240,6 +251,6 @@
      * Listener used to notify when the dialog is hidden
      */
     interface OnHideListener {
-        void onHide();
+        void onHide(boolean dismissUserSwitcher);
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index 7cd6adb..0f7c1ee 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -34,6 +34,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
 import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
 
 /**
@@ -116,7 +117,7 @@
         // Show unlock dialog for initial user
         if (hasTrustedDevice(initialUser)) {
             mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
-                    () -> dismissUserSwitcher());
+                    mOnHideListener);
         }
     }
 
@@ -162,7 +163,7 @@
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
         mSelectedUser = record;
         if (hasTrustedDevice(record.mInfo.id)) {
-            mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, () -> dismissUserSwitcher());
+            mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
             return;
         }
         if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -202,4 +203,18 @@
     private boolean hasTrustedDevice(int uid) {
         return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
     }
+
+    private OnHideListener mOnHideListener = new OnHideListener() {
+        @Override
+        public void onHide(boolean dismissUserSwitcher) {
+            if (dismissUserSwitcher) {
+                dismissUserSwitcher();
+            } else {
+                // Re-draw the parent view, otherwise the unlock dialog will not be removed from
+                // the screen immediately.
+                mParent.invalidate();
+            }
+
+        }
+    };
 }
diff --git a/packages/CarrierDefaultApp/tests/unit/Android.bp b/packages/CarrierDefaultApp/tests/unit/Android.bp
index 96144cf..5655abb 100644
--- a/packages/CarrierDefaultApp/tests/unit/Android.bp
+++ b/packages/CarrierDefaultApp/tests/unit/Android.bp
@@ -17,7 +17,6 @@
     certificate: "platform",
     libs: [
         "android.test.runner",
-        "telephony-common",
         "android.test.base",
     ],
     static_libs: [
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index b0e2700..1c0e409 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -62,7 +62,7 @@
                 android:exported="false" />
 
         <activity android:name=".DeleteStagedFileOnResult"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar"
+            android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 55ff591..a2fa461 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -135,6 +135,7 @@
 
         mEnableOk = true;
         mOk.setEnabled(true);
+        mOk.setFilterTouchesWhenObscured(true);
     }
 
     /**
diff --git a/packages/SettingsLib/HelpUtils/AndroidManifest.xml b/packages/SettingsLib/HelpUtils/AndroidManifest.xml
index 5240ce4..ccad6e4 100644
--- a/packages/SettingsLib/HelpUtils/AndroidManifest.xml
+++ b/packages/SettingsLib/HelpUtils/AndroidManifest.xml
@@ -16,6 +16,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.helputils">
+    package="com.android.settingslib.widget">
 
 </manifest>
diff --git a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
index e407d72..2d13b73 100644
--- a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
@@ -33,11 +33,11 @@
 import android.view.MenuItem;
 import android.view.MenuItem.OnMenuItemClickListener;
 
+import com.android.settingslib.widget.R;
+
 import java.net.URISyntaxException;
 import java.util.Locale;
 
-import com.android.settingslib.helputils.R;
-
 /**
  * Functions to easily prepare contextual help menu option items with an intent that opens up the
  * browser to a particular URL, while taking into account the preferred language and app version.
diff --git a/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml b/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
index d19a022..0975640 100644
--- a/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/AndroidManifest.xml
@@ -16,6 +16,6 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.settingslib.restrictedlockutils">
+          package="com.android.settingslib.widget">
 
 </manifest>
\ No newline at end of file
diff --git a/packages/SettingsLib/SearchWidget/res/values-km/strings.xml b/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
index f012e3a..7ac9cb1 100644
--- a/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-km/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="search_menu" msgid="1604061903696928905">"ការកំណត់ការ​ស្វែងរក"</string>
+    <string name="search_menu" msgid="1604061903696928905">"​ស្វែងរកការកំណត់"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index afdb105..2649544 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"Temps restant aproximat segons l\'ús que en fas: <xliff:g id="TIME_REMAINING">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en fas (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en fas"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"La bateria hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en facis (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> segons l\'ús que en facis"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Hauria de durar aproximadament fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Fins a les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Allarga la durada de la bateria després de les <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Temps restant inferior a <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index a74b4ae..16d7ea4 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -169,7 +169,7 @@
     <string name="tts_engine_security_warning" msgid="8786238102020223650">"Այս խոսքային սինթեզի գործիքը կարող է հավաքել այն ամենը, ինչ արտասանված է, այդ թվում` անձնական տվյալներ, ինչպիսիք են գաղտնաբառն ու բանկային քարտի համարները: Սկզբնաբյուրը <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> շարժիչն է: Միացնե՞լ խոսքի սինթեզի շարժիչի օգտագործումը:"</string>
     <string name="tts_engine_network_required" msgid="1190837151485314743">"Այս լեզուն պահանջում է աշխատող ցանցային կապ գրվածքից խոսք ելքի համար:"</string>
     <string name="tts_default_sample_string" msgid="4040835213373086322">"Սա խոսքային սինթեզի մի նմուշ է:"</string>
-    <string name="tts_status_title" msgid="7268566550242584413">"Լռելյայն լեզվի կարգավիճակը"</string>
+    <string name="tts_status_title" msgid="7268566550242584413">"Կանխադրված լեզվի կարգավիճակը"</string>
     <string name="tts_status_ok" msgid="1309762510278029765">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը լիովին աջակցվում է"</string>
     <string name="tts_status_requires_network" msgid="6042500821503226892">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը պահանջում է ցանցային կապ"</string>
     <string name="tts_status_not_supported" msgid="4491154212762472495">"<xliff:g id="LOCALE">%1$s</xliff:g>-ը չի աջակցվում"</string>
@@ -201,9 +201,9 @@
     <string name="vpn_settings_not_available" msgid="956841430176985598">"VPN-ի կարգավորումները հասանելի չեն այս օգտատիրոջը"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"Այս օգտատերը չի կարող փոխել մոդեմի ռեժիմի կարգավորումները"</string>
     <string name="apn_settings_not_available" msgid="7873729032165324000">"Մուտքի կետի անվան կարգավորումները հասանելի չեն այս օգտատիրոջը"</string>
-    <string name="enable_adb" msgid="7982306934419797485">"USB վրիպազերծում"</string>
+    <string name="enable_adb" msgid="7982306934419797485">"USB-ով վրիպազերծում"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"Միացնել վրիպազերծման ռեժիմը, երբ USB-ն միացված է"</string>
-    <string name="clear_adb_keys" msgid="4038889221503122743">"Չեղարկել USB վրիպազերծման լիազորումները"</string>
+    <string name="clear_adb_keys" msgid="4038889221503122743">"Չեղարկել USB-ով վրիպազերծման թույլտվությունները"</string>
     <string name="bugreport_in_power" msgid="7923901846375587241">"Սխալի հաղորդման դյուրանցում"</string>
     <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Գործարկման ցանկում ցույց տալ կոճակը՝ վրիպակների հաղորդման համար"</string>
     <string name="keep_screen_on" msgid="1146389631208760344">"Մնալ արթուն"</string>
@@ -263,9 +263,9 @@
     <string name="debug_view_attributes" msgid="6485448367803310384">"Միացնել ցուցադրման հատկանիշների ստուգումը"</string>
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Միշտ ակտիվացրած պահել բջջային տվյալները, նույնիսկ Wi‑Fi-ը միացրած ժամանակ (ցանցերի միջև արագ փոխարկման համար):"</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Օգտագործել սարքակազմի արագացման միացումը, եթե հասանելի է"</string>
-    <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB վրիպազերծումը:"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string>
-    <string name="adb_keys_warning_message" msgid="5659849457135841625">"Փակե՞լ USB-ի վրիպազերծման մուտքը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string>
+    <string name="adb_warning_title" msgid="6234463310896563253">"Թույլատրե՞լ USB-ով վրիպազերծումը:"</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB-ով վրիպազերծումը միայն ծրագրավորման նպատակների համար է: Օգտագործեք այն ձեր համակարգչից տվյալները ձեր սարք պատճենելու համար, առանց ծանուցման ձեր սարքի վրա ծրագրեր տեղադրելու և տվյալների մատյանը ընթերցելու համար:"</string>
+    <string name="adb_keys_warning_message" msgid="5659849457135841625">"Չեղարկե՞լ USB-ով վրիպազերծման հասանելիությունը` անջատելով այն բոլոր համակարգիչներից, որտեղ նախկինում թույլատրել էիք:"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"Ընդունե՞լ ծրագրավորման կարգավորումներ:"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"Այս կարգավորումները միայն ծրագրավորման նպատակների համար են նախատեսված: Դրանք կարող են խանգարել ձեր սարքի կամ ծրագրի աշխատանքին:"</string>
     <string name="verify_apps_over_usb_title" msgid="4177086489869041953">"Ստուգել հավելվածները USB-ի նկատմամբ"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 7fe1ef9..02c4c8c 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -336,8 +336,8 @@
     <string name="force_resizable_activities_summary" msgid="6667493494706124459">"Buat semua aktivitas dapat diubah ukurannya untuk banyak jendela, terlepas dari nilai manifes."</string>
     <string name="enable_freeform_support" msgid="1461893351278940416">"Aktifkan jendela berformat bebas"</string>
     <string name="enable_freeform_support_summary" msgid="8247310463288834487">"Aktifkan dukungan untuk jendela eksperimental berformat bebas."</string>
-    <string name="local_backup_password_title" msgid="3860471654439418822">"Sandi backup desktop"</string>
-    <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Saat ini backup desktop sepenuhnya tidak dilindungi"</string>
+    <string name="local_backup_password_title" msgid="3860471654439418822">"Sandi cadangan desktop"</string>
+    <string name="local_backup_password_summary_none" msgid="6951095485537767956">"Saat ini cadangan desktop penuh tidak dilindungi"</string>
     <string name="local_backup_password_summary_change" msgid="5376206246809190364">"Ketuk guna mengubah atau menghapus sandi untuk cadangan lengkap desktop"</string>
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"Sandi cadangan baru telah disetel"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"Sandi baru dan konfirmasinya tidak cocok."</string>
@@ -387,8 +387,8 @@
     <skip />
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan Anda (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> berdasarkan penggunaan Anda"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Akan bertahan kira-kira sampai <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Akan bertahan kira-kira sampai pukul <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Hingga <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Perpanjang masa pakai baterai hingga <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Tersisa kurang dari <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 68c0f17..a43e8d6 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -240,7 +240,7 @@
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
     <string name="select_private_dns_configuration_title" msgid="3700456559305263922">"DNS privato"</string>
     <string name="select_private_dns_configuration_dialog_title" msgid="9221994415765826811">"Seleziona modalità DNS privato"</string>
-    <string name="private_dns_mode_off" msgid="8236575187318721684">"Non attivo"</string>
+    <string name="private_dns_mode_off" msgid="8236575187318721684">"Off"</string>
     <string name="private_dns_mode_opportunistic" msgid="8314986739896927399">"Automatico"</string>
     <string name="private_dns_mode_provider" msgid="8354935160639360804">"Nome host del provider DNS privato"</string>
     <string name="private_dns_mode_provider_hostname_hint" msgid="2487492386970928143">"Inserisci il nome host del provider DNS"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index ead0bfb..a3e049c 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -342,7 +342,7 @@
     <string name="local_backup_password_toast_success" msgid="582016086228434290">"הוגדרה סיסמת גיבוי חדשה"</string>
     <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"הסיסמה החדשה והאישור אינם תואמים"</string>
     <string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"הגדרת סיסמת גיבוי נכשלה"</string>
-    <string name="loading_injected_setting_summary" msgid="4095178591461231376">"טוען…"</string>
+    <string name="loading_injected_setting_summary" msgid="4095178591461231376">"בטעינה…"</string>
   <string-array name="color_mode_names">
     <item msgid="2425514299220523812">"דינמי (ברירת מחדל)"</item>
     <item msgid="8446070607501413455">"טבעי"</item>
@@ -405,7 +405,7 @@
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> עד לטעינה מלאה"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> עד לטעינה מלאה"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"לא ידוע"</string>
-    <string name="battery_info_status_charging" msgid="1705179948350365604">"טוען"</string>
+    <string name="battery_info_status_charging" msgid="1705179948350365604">"בטעינה"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"בטעינה"</string>
     <string name="battery_info_status_discharging" msgid="310932812698268588">"לא בטעינה"</string>
     <string name="battery_info_status_not_charging" msgid="8523453668342598579">"המכשיר מחובר, אבל לא ניתן לטעון עכשיו"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index e5d29bf..50543d8 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"残り時間: 約 <xliff:g id="TIME_REMAINING">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"電池切れの推定時間: <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(使用状況に基づく)"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>(<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"電池切れの推定時刻: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"<xliff:g id="TIME">%1$s</xliff:g> まで"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"<xliff:g id="TIME">%1$s</xliff:g>まで電池消費量を抑える"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"残り時間: <xliff:g id="THRESHOLD">%1$s</xliff:g>未満"</string>
diff --git a/packages/SettingsLib/res/values-ko/arrays.xml b/packages/SettingsLib/res/values-ko/arrays.xml
index e2d8e02..15e11db 100644
--- a/packages/SettingsLib/res/values-ko/arrays.xml
+++ b/packages/SettingsLib/res/values-ko/arrays.xml
@@ -226,7 +226,7 @@
   </string-array>
   <string-array name="enable_opengl_traces_entries">
     <item msgid="3191973083884253830">"없음"</item>
-    <item msgid="9089630089455370183">"로그캣"</item>
+    <item msgid="9089630089455370183">"Logcat"</item>
     <item msgid="5397807424362304288">"Systrace(그래픽)"</item>
     <item msgid="1340692776955662664">"glGetError의 스택 호출"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index ed88bc5..1cee8fb 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"ເຫຼືອອີກປະມານ <xliff:g id="TIME_REMAINING">%1$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> based on your usage (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> based on your usage"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Should last until about <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Should last until about <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> ໂດຍອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"ໜ້າຈະໃຊ້ໄດ້ຈົນຮອດປະມານ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"ຈົນກວ່າຈະຮອດ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"ຂະຫຍາຍອາຍຸແບັດເຕີຣີກາຍ <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"ຍັງເຫຼືອໜ້ອຍກວ່າ <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 8feb5ab..96aebc5 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -403,7 +403,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Iespējams, ierīce drīz izslēgsies (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> — <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Atlikušais laiks līdz pilnai uzlādei: <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>, kamēr pilnībā uzlādēts"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> līdz pilnīgai uzlādei"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Nezināms"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Uzlāde"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"notiek uzlāde"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index dda3462..d93304f 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -153,7 +153,7 @@
     <string name="launch_defaults_some" msgid="313159469856372621">"Поставени се некои стандардни вредности"</string>
     <string name="launch_defaults_none" msgid="4241129108140034876">"Нема поставено стандардни вредности"</string>
     <string name="tts_settings" msgid="8186971894801348327">"Поставки на текст-во-говор"</string>
-    <string name="tts_settings_title" msgid="1237820681016639683">"Излез текст-во-говор"</string>
+    <string name="tts_settings_title" msgid="1237820681016639683">"Претворање текст во говор"</string>
     <string name="tts_default_rate_title" msgid="6030550998379310088">"Брзина на говор"</string>
     <string name="tts_default_rate_summary" msgid="4061815292287182801">"Брзина со која се кажува текстот"</string>
     <string name="tts_default_pitch_title" msgid="6135942113172488671">"Интензитет"</string>
@@ -167,7 +167,7 @@
     <string name="tts_install_data_title" msgid="4264378440508149986">"Инсталирај гласовни податоци"</string>
     <string name="tts_install_data_summary" msgid="5742135732511822589">"Инсталирај ги гласовните податоци потребни за синтеза на говор"</string>
     <string name="tts_engine_security_warning" msgid="8786238102020223650">"Овој софтвер за синтеза на говор може да го собере сиот текст што ќе се говори, вклучувајќи и лични податоци како што се лозинки и броеви на кредитни картички. Тоа го прави апликацијата <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Употреби го овој софтвер за синтеза на говор?"</string>
-    <string name="tts_engine_network_required" msgid="1190837151485314743">"За овој јазик е потребно поврување со мрежа што функционира на излез за текст-во-говор."</string>
+    <string name="tts_engine_network_required" msgid="1190837151485314743">"За претворање текст во говор на овој јазик, потребна е активна мрежна врска."</string>
     <string name="tts_default_sample_string" msgid="4040835213373086322">"Ова е пример на синтеза на говор"</string>
     <string name="tts_status_title" msgid="7268566550242584413">"Статус на стандарден јазик"</string>
     <string name="tts_status_ok" msgid="1309762510278029765">"<xliff:g id="LOCALE">%1$s</xliff:g> е целосно поддржан"</string>
@@ -375,7 +375,7 @@
     <string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"Девтераномалија (слепило за црвена и зелена)"</string>
     <string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"Протаномалија (слепило за црвена и зелена)"</string>
     <string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"Тританомалија (слепило за сина и жолта)"</string>
-    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Корекција на боја"</string>
+    <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Корекција на бои"</string>
     <string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Функцијата е експериментална и може да влијае на изведбата."</string>
     <string name="daltonizer_type_overridden" msgid="3116947244410245916">"Прескокнато според <xliff:g id="TITLE">%1$s</xliff:g>"</string>
     <string name="power_remaining_settings_home_page" msgid="4845022416859002011">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index f1934c3..b2e1cd6 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -153,7 +153,7 @@
     <string name="launch_defaults_some" msgid="313159469856372621">"केही पूर्वनिर्धारितहरू सेट गरिएका छन्"</string>
     <string name="launch_defaults_none" msgid="4241129108140034876">"कुनै पूर्वनिर्धारित सेट गरिएको छैन"</string>
     <string name="tts_settings" msgid="8186971894801348327">"पाठ-वाचन सेटिङहरू"</string>
-    <string name="tts_settings_title" msgid="1237820681016639683">"पाठवाचकको उत्पादन"</string>
+    <string name="tts_settings_title" msgid="1237820681016639683">"पाठवाचकको आउटपुट"</string>
     <string name="tts_default_rate_title" msgid="6030550998379310088">"वाणी दर"</string>
     <string name="tts_default_rate_summary" msgid="4061815292287182801">"पाठ वाचन हुने गति"</string>
     <string name="tts_default_pitch_title" msgid="6135942113172488671">"पिच"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 5725c22..f9e0d39 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -402,7 +402,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"ଖୁବ୍ ଶୀଘ୍ର ଟାବଲେଟ୍‌ଟି ବନ୍ଦ ହୋଇଯାଇପାରେ (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"ଖୁବ୍ ଶୀଘ୍ର ଡିଭାଇସ୍‌ଟି ବନ୍ଦ ହୋଇଯାଇପାରେ(<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ସମ୍ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାପାଇଁ <xliff:g id="TIME">%1$s</xliff:g> ଅବଶିଷ୍ଟ ଅଛି"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"ସମ୍ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବାପାଇଁ <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> ପୂର୍ଣ୍ଣ ଚାର୍ଜ ହେବା ପର୍ଯ୍ୟନ୍ତ"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"ଅଜ୍ଞାତ"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"ଚାର୍ଜ ହେଉଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index faccda7..b5acd8e 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -388,7 +388,7 @@
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ"</string>
     <string name="power_discharge_by" msgid="6453537733650125582">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਚੱਲੇਗਾ"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"ਲਗਭਗ <xliff:g id="TIME">%1$s</xliff:g> ਤੱਕ ਚੱਲੇਗੀ"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"<xliff:g id="TIME">%1$s</xliff:g> ਤੱਕ"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"ਬੈਟਰੀ ਲਾਈਫ਼ <xliff:g id="TIME">%1$s</xliff:g> ਤੋਂ ਬਾਅਦ ਤੱਕ ਵਧਾਓ"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"<xliff:g id="THRESHOLD">%1$s</xliff:g> ਤੋਂ ਘੱਟ ਸਮਾਂ ਬਾਕੀ"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 501cf50..c076d32 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -387,8 +387,8 @@
     <skip />
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> com base na sua utilização."</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)."</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g>."</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Deve durar até cerca da(s) <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Até à(s) <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Prolongar a autonomia da bateria após <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Resta(m) menos de <xliff:g id="THRESHOLD">%1$s</xliff:g>."</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index e28825c..d77e1eb 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -385,10 +385,10 @@
     <string name="power_discharging_duration_enhanced" msgid="1992003260664804080">"Zostáva približne <xliff:g id="TIME_REMAINING">%1$s</xliff:g> – závisí to od intenzity využitia (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
     <!-- no translation found for power_remaining_duration_only_short (9183070574408359726) -->
     <skip />
-    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g> v závislosti od využitia (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g> v závislosti od využitia"</string>
-    <string name="power_discharge_by" msgid="6453537733650125582">"Mal by vydržať približne <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Mal by vydržať približne do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Podľa vášho využitia (<xliff:g id="LEVEL">%2$s</xliff:g>) vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Podľa vášho využitia vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by" msgid="6453537733650125582">"Vydrží asi do <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Vydrží asi do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Predĺžiť výdrž batérie minimálne do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Zostáva menej ako <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
@@ -402,8 +402,8 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"Tablet sa môže čoskoro vypnúť (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Zariadenie sa môže čoskoro vypnúť (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Zostávajúci čas do úplného nabitia: <xliff:g id="TIME">%1$s</xliff:g>"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> do úplného nabitia"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g> do úplného nabitia"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Neznáme"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Nabíja sa"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"nabíja sa"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index b16d5d9..263b4d0 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -388,7 +388,7 @@
     <string name="power_discharge_by_enhanced" msgid="2095821536747992464">"Naprava bi morala glede na način uporabe (<xliff:g id="LEVEL">%2$s</xliff:g>) delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_enhanced" msgid="2175151772952365149">"Naprava bi morala glede na način uporabe delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by" msgid="6453537733650125582">"Naprava bi morala delovati do približno <xliff:g id="TIME">%1$s</xliff:g> (<xliff:g id="LEVEL">%2$s</xliff:g>)"</string>
-    <string name="power_discharge_by_only" msgid="107616694963545745">"Naprava bi morala delovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
+    <string name="power_discharge_by_only" msgid="107616694963545745">"Moralo bi zadostovati do približno <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_discharge_by_only_short" msgid="1372817269546888804">"Do <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_suggestion_extend_battery" msgid="4401408879069551485">"Podaljšanje časa delovanja akumulatorja za dlje kot <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <string name="power_remaining_less_than_duration_only" msgid="5996752448813295329">"Preostalo manj kot <xliff:g id="THRESHOLD">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 57e690d..8f72ab6 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -264,7 +264,7 @@
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"வைஃபை இயங்கும் போதும் (வேகமான நெட்வொர்க் மாற்றத்திற்கு), மொபைல் டேட்டாவை எப்போதும் இயக்கத்தில் வைக்கும்."</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"வன்பொருள் விரைவுப்படுத்துதல் இணைப்பு முறை கிடைக்கும் போது, அதைப் பயன்படுத்தும்"</string>
     <string name="adb_warning_title" msgid="6234463310896563253">"USB பிழைதிருத்தத்தை அனுமதிக்கவா?"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB பிழைதிருத்தம் மேம்படுத்தல் நோக்கங்களுக்காக மட்டுமே. அதை உங்கள் கணினி மற்றும் சாதனத்திற்கு இடையில் தரவை நகலெடுக்கவும், அறிவிப்பு இல்லாமல் உங்கள் சாதனத்தில் பயன்பாடுகளை நிறுவவும், பதிவு தரவைப் படிக்கவும் பயன்படுத்தவும்."</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB பிழைதிருத்தம் மேம்படுத்தல் நோக்கங்களுக்காக மட்டுமே. அதை உங்கள் கணினி மற்றும் சாதனத்திற்கு இடையில் தரவை நகலெடுக்கவும், அறிவிப்பு இல்லாமல் உங்கள் சாதனத்தில் ஆப்ஸை நிறுவவும், பதிவு தரவைப் படிக்கவும் பயன்படுத்தவும்."</string>
     <string name="adb_keys_warning_message" msgid="5659849457135841625">"நீங்கள் ஏற்கனவே அனுமதித்த எல்லா கணினிகளிலிருந்தும் USB பிழைத்திருத்தத்திற்கான அணுகலைத் திரும்பப்பெற வேண்டுமா?"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"மேம்பட்ட அமைப்புகளை அனுமதிக்கவா?"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"இந்த அமைப்பு மேம்பட்டப் பயன்பாட்டிற்காக மட்டுமே. உங்கள் சாதனம் மற்றும் அதில் உள்ள பயன்பாடுகளைச் சிதைக்கும் அல்லது தவறாகச் செயல்படும் வகையில் பாதிப்பை ஏற்படுத்தும்."</string>
@@ -402,7 +402,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"டேப்லெட் விரைவில் ஆஃப் ஆகக்கூடும் (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"சாதனம் விரைவில் ஆஃப் ஆகக்கூடும் (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"முழு சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g> ஆகும்"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"முழு சார்ஜாக <xliff:g id="TIME">%1$s</xliff:g>ம் ஆகும்"</string>
     <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - முழு சார்ஜாக <xliff:g id="TIME">%2$s</xliff:g> ஆகும்"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"அறியப்படாத"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"சார்ஜ் ஆகிறது"</string>
@@ -437,7 +437,7 @@
     <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"சாதன மொழிகளைப் பயன்படுத்து"</string>
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> க்கான அமைப்புகளைத் திறப்பதில் தோல்வி"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"இந்த உள்ளீட்டு முறையானது, கடவுச்சொற்கள் மற்றும் கிரெடிட் கார்டு எண்கள் போன்ற தனிப்பட்ட தகவல் உள்பட நீங்கள் உள்ளிடும் எல்லா உரையையும் சேகரிக்கக்கூடும். இது <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> பயன்பாட்டிலிருந்து வந்துள்ளது. இந்த உள்ளீட்டு முறையைப் பயன்படுத்தவா?"</string>
-    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"குறிப்பு: மறுதொடக்கம் செய்த பிறகு, மொபைலைத் திறக்கும் வரை இந்தப் பயன்பாட்டால் தொடங்க முடியாது"</string>
+    <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"குறிப்பு: மறுதொடக்கம் செய்த பிறகு, மொபைலைத் திறக்கும் வரை இந்த ஆப்ஸால் தொடங்க முடியாது"</string>
     <string name="ims_reg_title" msgid="7609782759207241443">"IMS பதிவின் நிலை"</string>
     <string name="ims_reg_status_registered" msgid="933003316932739188">"பதிவு செய்யப்பட்டது"</string>
     <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"பதிவு செய்யப்படவில்லை"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 75344c4..e591121 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -414,7 +414,7 @@
     <string name="disabled" msgid="9206776641295849915">"నిలిపివేయబడింది"</string>
     <string name="external_source_trusted" msgid="2707996266575928037">"అనుమతించినవి"</string>
     <string name="external_source_untrusted" msgid="2677442511837596726">"అనుమతించబడలేదు"</string>
-    <string name="install_other_apps" msgid="6986686991775883017">"తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయండి"</string>
+    <string name="install_other_apps" msgid="6986686991775883017">"తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయడం"</string>
     <string name="home" msgid="3256884684164448244">"సెట్టింగ్‌ల హోమ్"</string>
   <string-array name="battery_labels">
     <item msgid="8494684293649631252">"0%"</item>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 0b184d2..b3bef9d 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -403,7 +403,7 @@
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"อุปกรณ์อาจปิดเครื่องในไม่ช้า (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
     <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"อีก <xliff:g id="TIME">%1$s</xliff:g> จึงจะชาร์จเต็ม"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> จนกว่าจะชาร์จเต็ม"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g>จนกว่าจะชาร์จเต็ม"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"ไม่ทราบ"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"กำลังชาร์จ"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"กำลังชาร์จ"</string>
diff --git a/packages/SettingsLib/res/values-uz/arrays.xml b/packages/SettingsLib/res/values-uz/arrays.xml
index 164aa93..4bc995f 100644
--- a/packages/SettingsLib/res/values-uz/arrays.xml
+++ b/packages/SettingsLib/res/values-uz/arrays.xml
@@ -241,7 +241,7 @@
     <item msgid="2355151170975410323">"“<xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g>” buyrug‘ida"</item>
   </string-array>
   <string-array name="debug_hw_overdraw_entries">
-    <item msgid="8190572633763871652">"YOQILMAGAN"</item>
+    <item msgid="8190572633763871652">"Yoqilmagan"</item>
     <item msgid="7688197031296835369">"Ortiqcha chizilgan joylarni ko‘rsatish"</item>
     <item msgid="2290859360633824369">"Muayyan rangdagi hududlarni ajratib belgilash"</item>
   </string-array>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 5dca475..0d3e8de 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -201,7 +201,7 @@
     <string name="vpn_settings_not_available" msgid="956841430176985598">"Ushbu foydalanuvchi uchun VPN sozlamalari mavjud emas"</string>
     <string name="tethering_settings_not_available" msgid="6765770438438291012">"Ushbu foydalanuvchi uchun Modem rejimi sozlamalari mavjud emas"</string>
     <string name="apn_settings_not_available" msgid="7873729032165324000">"Ushbu foydalanuvchi uchun Internetga kirish nuqtasi (APN) sozlamalari mavjud emas"</string>
-    <string name="enable_adb" msgid="7982306934419797485">"USB orqali nosozliklarni tuzatish"</string>
+    <string name="enable_adb" msgid="7982306934419797485">"USB orqali nosozliklarni aniqlash"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"USB orqali kompyuterga ulanganda tuzatish rejimi yoqilsin"</string>
     <string name="clear_adb_keys" msgid="4038889221503122743">"USB orqali nosozliklarni tuzatishni taqiqlash"</string>
     <string name="bugreport_in_power" msgid="7923901846375587241">"Xatoliklar hisoboti"</string>
@@ -264,7 +264,7 @@
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"Mobil internet har doim yoniq tursin, hatto Wi-Fi yoniq bo‘lsa ham (bir tarmoqdan ikkinchisiga tezroq o‘tish uchun)."</string>
     <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"Modem rejimida apparatli tezlashtirishdan foydalanish (agar mavjud bo‘lsa)"</string>
     <string name="adb_warning_title" msgid="6234463310896563253">"USB orqali nosozliklarni tuzatishga ruxsat berilsinmi?"</string>
-    <string name="adb_warning_message" msgid="7316799925425402244">"USB orqali nosozliklarni tuzatish faqat dasturlash maqsadlarida yoqiladi. Undan ma‘lumotlarni qurilmangiz va kompyuter o‘rtasida ko‘chirish, ilovalarni xabarnomasiz o‘rnatish va jurnal ma‘lumotlarini o‘qish uchun foydalaniladi."</string>
+    <string name="adb_warning_message" msgid="7316799925425402244">"USB orqali nosozliklarni aniqlash faqat dasturlash maqsadlarida yoqiladi. Undan maʼlumotlarni qurilmangiz va kompyuter o‘rtasida ko‘chirish, ilovalarni xabarnomasiz o‘rnatish va jurnal maʼlumotlarini o‘qish uchun foydalaniladi."</string>
     <string name="adb_keys_warning_message" msgid="5659849457135841625">"USB orqali nosozliklarni tuzatishga berilgan ruxsat siz hisobingizga kirgan barcha kompyuterlar uchun bekor qilinsinmi?"</string>
     <string name="dev_settings_warning_title" msgid="7244607768088540165">"Dasturlash sozlamalariga ruxsat berilsinmi?"</string>
     <string name="dev_settings_warning_message" msgid="2298337781139097964">"Bu sozlamalar faqat dasturlash maqsadlariga mo‘ljallangan. Shuning uchun, ular qurilmangizga va undagi ilovalariga shikast yetkazib, noto‘g‘ri ishlashiga sabab bo‘lishi mumkin."</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 5c06c80..003c457 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -402,8 +402,8 @@
     <string name="power_remaining_duration_shutdown_imminent" product="tablet" msgid="7466484148515796216">"Máy tính bảng có thể sắp tắt (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_remaining_duration_shutdown_imminent" product="device" msgid="603933521600231649">"Thiết bị có thể sắp tắt (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
     <string name="power_charging" msgid="1779532561355864267">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
-    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"Còn <xliff:g id="TIME">%1$s</xliff:g> cho tới khi được sạc đầy"</string>
-    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> cho tới khi được sạc đầy"</string>
+    <string name="power_remaining_charging_duration_only" msgid="1421102457410268886">"<xliff:g id="TIME">%1$s</xliff:g> nữa sẽ được sạc đầy"</string>
+    <string name="power_charging_duration" msgid="4676999980973411875">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> nữa sẽ được sạc đầy"</string>
     <string name="battery_info_status_unknown" msgid="196130600938058547">"Không xác định"</string>
     <string name="battery_info_status_charging" msgid="1705179948350365604">"Đang sạc"</string>
     <string name="battery_info_status_charging_lower" msgid="8689770213898117994">"đang sạc"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 460142c..bcf1f2d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -60,7 +60,7 @@
     <string name="speed_label_medium" msgid="3175763313268941953">"适中"</string>
     <string name="speed_label_fast" msgid="7715732164050975057">"快"</string>
     <string name="speed_label_very_fast" msgid="2265363430784523409">"很快"</string>
-    <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+    <string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="bluetooth_disconnected" msgid="6557104142667339895">"已断开连接"</string>
     <string name="bluetooth_disconnecting" msgid="8913264760027764974">"正在断开连接..."</string>
     <string name="bluetooth_connecting" msgid="8555009514614320497">"正在连接..."</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 84f5a04..98db7c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -27,6 +27,7 @@
 import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.EventLog;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -799,10 +800,9 @@
                         == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
                     mDevice.getBluetoothClass().getDeviceClass()
                         == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
-                } else {
-                    mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+                    EventLog.writeEvent(0x534e4554, "138529441", -1, "");
                 }
+                mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
             }
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
index eeb6cb0..ea8a62f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -49,6 +49,7 @@
     private final ArraySet<String> mWhitelistedApps = new ArraySet<>();
     private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>();
     private final ArraySet<String> mSysWhitelistedAppsExceptIdle = new ArraySet<>();
+    private final ArraySet<String> mDefaultActiveApps = new ArraySet<>();
 
     public PowerWhitelistBackend(Context context) {
         this(context, IDeviceIdleController.Stub.asInterface(
@@ -90,17 +91,7 @@
         // should be automatically whitelisted (otherwise user may be able to set restriction on
         // them, leading to bad device behavior.)
 
-        final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY);
-        final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
-                true /* updateIfNeeded */);
-        if (hasTelephony && defaultSms != null && TextUtils.equals(pkg,
-                defaultSms.getPackageName())) {
-            return true;
-        }
-
-        final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mAppContext);
-        if (hasTelephony && TextUtils.equals(pkg, defaultDialer)) {
+        if (mDefaultActiveApps.contains(pkg)) {
             return true;
         }
 
@@ -166,6 +157,7 @@
         mSysWhitelistedApps.clear();
         mSysWhitelistedAppsExceptIdle.clear();
         mWhitelistedApps.clear();
+        mDefaultActiveApps.clear();
         if (mDeviceIdleService == null) {
             return;
         }
@@ -183,6 +175,21 @@
             for (String app : sysWhitelistedAppsExceptIdle) {
                 mSysWhitelistedAppsExceptIdle.add(app);
             }
+            final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TELEPHONY);
+            final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
+                    true /* updateIfNeeded */);
+            final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(
+                    mAppContext);
+
+            if (hasTelephony) {
+                if (defaultSms != null) {
+                    mDefaultActiveApps.add(defaultSms.getPackageName());
+                }
+                if (!TextUtils.isEmpty(defaultDialer)) {
+                    mDefaultActiveApps.add(defaultDialer);
+                }
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to reach IDeviceIdleController", e);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
deleted file mode 100644
index fcf2363..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixin.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-/**
- * Framework mixin is deprecated, use the compat version instead.
- *
- * @deprecated
- */
-@Deprecated
-public class FooterPreferenceMixin implements LifecycleObserver, SetPreferenceScreen {
-
-    private final PreferenceFragment mFragment;
-    private FooterPreference mFooterPreference;
-
-    public FooterPreferenceMixin(PreferenceFragment fragment, Lifecycle lifecycle) {
-        mFragment = fragment;
-        lifecycle.addObserver(this);
-    }
-
-    @Override
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        if (mFooterPreference != null) {
-            preferenceScreen.addPreference(mFooterPreference);
-        }
-    }
-
-    /**
-     * Creates a new {@link FooterPreference}.
-     */
-    public FooterPreference createFooterPreference() {
-        final PreferenceScreen screen = mFragment.getPreferenceScreen();
-        if (mFooterPreference != null && screen != null) {
-            screen.removePreference(mFooterPreference);
-        }
-        mFooterPreference = new FooterPreference(getPrefContext());
-
-        if (screen != null) {
-            screen.addPreference(mFooterPreference);
-        }
-        return mFooterPreference;
-    }
-
-    /**
-     * Returns an UI context with theme properly set for new Preference objects.
-     */
-    private Context getPrefContext() {
-        return mFragment.getPreferenceManager().getContext();
-    }
-
-    public boolean hasFooter() {
-        return mFooterPreference != null;
-    }
-}
-
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
deleted file mode 100644
index d45e56d..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import android.content.Context;
-
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
-
-public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
-
-    private final PreferenceFragmentCompat mFragment;
-    private FooterPreference mFooterPreference;
-
-    public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
-        mFragment = fragment;
-        lifecycle.addObserver(this);
-    }
-
-    @Override
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        if (mFooterPreference != null) {
-            preferenceScreen.addPreference(mFooterPreference);
-        }
-    }
-
-    /**
-     * Creates a new {@link FooterPreference}.
-     */
-    public FooterPreference createFooterPreference() {
-        final PreferenceScreen screen = mFragment.getPreferenceScreen();
-        if (mFooterPreference != null && screen != null) {
-            screen.removePreference(mFooterPreference);
-        }
-        mFooterPreference = new FooterPreference(getPrefContext());
-
-        if (screen != null) {
-            screen.addPreference(mFooterPreference);
-        }
-        return mFooterPreference;
-    }
-
-    /**
-     * Returns an UI context with theme properly set for new Preference objects.
-     */
-    private Context getPrefContext() {
-        return mFragment.getPreferenceManager().getContext();
-    }
-
-    public boolean hasFooter() {
-        return mFooterPreference != null;
-    }
-}
-
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
index 44ee423..3a571f9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -117,6 +117,8 @@
         final String testSms = "com.android.test.defaultsms";
         ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(testSms, "receiver"));
 
+        mPowerWhitelistBackend.refreshList();
+
         assertThat(mPowerWhitelistBackend.isWhitelisted(testSms)).isTrue();
         assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testSms)).isTrue();
     }
@@ -126,6 +128,8 @@
         final String testDialer = "com.android.test.defaultdialer";
         ShadowDefaultDialerManager.setDefaultDialerApplication(testDialer);
 
+        mPowerWhitelistBackend.refreshList();
+
         assertThat(mPowerWhitelistBackend.isWhitelisted(testDialer)).isTrue();
         assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testDialer)).isTrue();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
deleted file mode 100644
index 601da051..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class FooterPreferenceMixinCompatTest {
-
-    @Mock
-    private PreferenceFragmentCompat mFragment;
-    @Mock
-    private PreferenceScreen mScreen;
-
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-    private FooterPreferenceMixinCompat mMixin;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLifecycleOwner = () -> mLifecycle;
-        mLifecycle = new Lifecycle(mLifecycleOwner);
-        when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
-        when(mFragment.getPreferenceManager().getContext())
-                .thenReturn(RuntimeEnvironment.application);
-        mMixin = new FooterPreferenceMixinCompat(mFragment, mLifecycle);
-    }
-
-    @Test
-    public void createFooter_screenNotAvailable_noCrash() {
-        assertThat(mMixin.createFooterPreference()).isNotNull();
-    }
-
-    @Test
-    public void createFooter_screenAvailable_canAttachToScreen() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooter_screenAvailableDelayed_canAttachToScreen() {
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        mLifecycle.setPreferenceScreen(mScreen);
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooterTwice_screenAvailable_replaceOldFooter() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        mMixin.createFooterPreference();
-        mMixin.createFooterPreference();
-
-        verify(mScreen).removePreference(any(FooterPreference.class));
-        verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
deleted file mode 100644
index 7ae5d2d..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
-import com.android.settingslib.core.lifecycle.Lifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class FooterPreferenceMixinTest {
-
-    @Mock
-    private PreferenceFragment mFragment;
-    @Mock
-    private PreferenceScreen mScreen;
-
-    private LifecycleOwner mLifecycleOwner;
-    private Lifecycle mLifecycle;
-    private FooterPreferenceMixin mMixin;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mLifecycleOwner = () -> mLifecycle;
-        mLifecycle = new Lifecycle(mLifecycleOwner);
-        when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
-        when(mFragment.getPreferenceManager().getContext())
-                .thenReturn(RuntimeEnvironment.application);
-        mMixin = new FooterPreferenceMixin(mFragment, mLifecycle);
-    }
-
-    @Test
-    public void createFooter_screenNotAvailable_noCrash() {
-        assertThat(mMixin.createFooterPreference()).isNotNull();
-    }
-
-    @Test
-    public void createFooter_screenAvailable_canAttachToScreen() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooter_screenAvailableDelayed_canAttachToScreen() {
-        final FooterPreference preference = mMixin.createFooterPreference();
-
-        mLifecycle.setPreferenceScreen(mScreen);
-
-        assertThat(preference).isNotNull();
-        verify(mScreen).addPreference(preference);
-    }
-
-    @Test
-    public void createFooterTwice_screenAvailable_replaceOldFooter() {
-        when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
-
-        mMixin.createFooterPreference();
-        mMixin.createFooterPreference();
-
-        verify(mScreen).removePreference(any(FooterPreference.class));
-        verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
-    }
-
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 0c49f63..1527de1 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,5 +69,6 @@
         Settings.Global.ZEN_DURATION,
         Settings.Global.CHARGING_VIBRATION_ENABLED,
         Settings.Global.AWARE_ALLOWED,
+        Settings.Global.NOTIFICATION_BUBBLES,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 9be636d..4a0ed6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -148,5 +148,6 @@
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
         VALIDATORS.put(
                 Global.POWER_BUTTON_VERY_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 1));
+        VALIDATORS.put(Global.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 00b2563..365923e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1114,6 +1114,9 @@
                 Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                 GlobalSettingsProto.Notification.SNOOZE_OPTIONS);
         dumpSetting(s, p,
+                Settings.Global.NOTIFICATION_BUBBLES,
+                GlobalSettingsProto.Notification.BUBBLES);
+        dumpSetting(s, p,
                 Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
                 GlobalSettingsProto.Notification.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS);
         dumpSetting(s, p,
@@ -2225,7 +2228,7 @@
                 Settings.Secure.NOTIFICATION_BADGING,
                 SecureSettingsProto.Notification.BADGING);
         dumpSetting(s, p,
-                Settings.Secure.NOTIFICATION_BUBBLES,
+                Settings.Global.NOTIFICATION_BUBBLES,
                 SecureSettingsProto.Notification.BUBBLES);
         dumpSetting(s, p,
                 Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4d71e72b5..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,
@@ -3186,7 +3186,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 182;
+            private static final int SETTINGS_VERSION = 183;
 
             private final int mUserId;
 
@@ -4203,19 +4203,7 @@
 
                 if (currentVersion == 173) {
                     // Version 173: Set the default value for Secure Settings: NOTIFICATION_BUBBLES
-
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
-                    final Setting bubblesSetting = secureSettings.getSettingLocked(
-                            Secure.NOTIFICATION_BUBBLES);
-
-                    if (bubblesSetting.isNull()) {
-                        secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                                getContext().getResources().getBoolean(
-                                        R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                true, SettingsState.SYSTEM_PACKAGE_NAME);
-                    }
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 174;
                 }
 
@@ -4339,14 +4327,9 @@
                 if (currentVersion == 179) {
                     // Version 178: Reset the default for Secure Settings: NOTIFICATION_BUBBLES
                     // This is originally set in version 173, however, the default value changed
-                    // so this step is to ensure the value is updated to the correct defaulte
-                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    // so this step is to ensure the value is updated to the correct default.
 
-                    secureSettings.insertSettingLocked(Secure.NOTIFICATION_BUBBLES,
-                            getContext().getResources().getBoolean(
-                                    R.bool.def_notification_bubbles) ? "1" : "0", null,
-                                    true, SettingsState.SYSTEM_PACKAGE_NAME);
-
+                    // Removed. Moved NOTIFICATION_BUBBLES to Global Settings.
                     currentVersion = 180;
                 }
 
@@ -4400,6 +4383,20 @@
                     currentVersion = 182;
                 }
 
+                if (currentVersion == 182) {
+                    // Remove secure bubble settings.
+                    getSecureSettingsLocked(userId).deleteSettingLocked(
+                            Secure.NOTIFICATION_BUBBLES);
+
+                    // Add global bubble settings.
+                    getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
+                            getContext().getResources().getBoolean(
+                                    R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
+                            true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+                    currentVersion = 183;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index d7eb7e95..6ea3db3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -308,7 +308,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,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 12ca92f..57e22db 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -30,6 +30,7 @@
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.provider.Settings;
 import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
@@ -47,6 +48,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -270,5 +272,17 @@
             }
             return result;
         }
+
+        @Override
+        public Bundle call(String method, String request, Bundle args) {
+            for (Object[] resultRow : RESULT_ROWS) {
+                if (Objects.equals(request, resultRow[0])) {
+                    final Bundle res = new Bundle();
+                    res.putString("value", String.valueOf(resultRow[1]));
+                    return res;
+                }
+            }
+            return Bundle.EMPTY;
+        }
     }
 }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index 183f599..d67a9bc 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -33,6 +33,7 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -693,6 +694,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationModeChanges_viaFrontEndApi() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -735,6 +737,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowed_disableProviders() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -766,6 +769,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowed_enableAndDisable() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
@@ -788,6 +792,7 @@
     }
 
     @Test
+    @Ignore("b/140250974")
     public void testLocationProvidersAllowedLocked_invalidInput() throws Exception {
         setStringViaFrontEndApiSetting(
                 SETTING_TYPE_SECURE,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 8c0108d..602fe3e 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -1264,9 +1264,7 @@
         }
         return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .addExtras(sNotificationBundle)
-                .setSmallIcon(
-                        isTv(context) ? R.drawable.ic_bug_report_black_24dp
-                                : com.android.internal.R.drawable.stat_sys_adb)
+                .setSmallIcon(R.drawable.ic_bug_report_black_24dp)
                 .setLocalOnly(true)
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index 4ba5146..d2f168e 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -52,7 +52,6 @@
 import com.android.internal.app.AlertController;
 
 import java.io.IOException;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -154,10 +153,7 @@
             if (which == mCursor.getCount() + mStaticItemCount) {
                 // The "Add new ringtone" item was clicked. Start a file picker intent to select
                 // only audio files (MIME type "audio/*")
-                final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
-                chooseFile.setType("audio/*");
-                chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
-                        new String[] { "audio/*", "application/ogg" });
+                final Intent chooseFile = getMediaFilePickerIntent();
                 startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE);
                 return;
             }
@@ -375,7 +371,8 @@
             setSuccessResultWithRingtone(getCurrentlySelectedRingtoneUri());
         }
         // If external storage is available, add a button to install sounds from storage.
-        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+        if (resolvesMediaFilePicker()
+                && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
             addNewSoundItem(listView);
         }
 
@@ -633,6 +630,18 @@
         return ringtoneManagerPos + mStaticItemCount;
     }
 
+    private Intent getMediaFilePickerIntent() {
+        final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
+        chooseFile.setType("audio/*");
+        chooseFile.putExtra(Intent.EXTRA_MIME_TYPES,
+                new String[] { "audio/*", "application/ogg" });
+        return chooseFile;
+    }
+
+    private boolean resolvesMediaFilePicker() {
+        return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null;
+    }
+
     private static class LocalizedCursor extends CursorWrapper {
 
         final int mTitleIndex;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 4c52b132..37fefc2 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -71,15 +71,21 @@
         "telephony-common",
     ],
 
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
     kotlincflags: ["-Xjvm-default=enable"],
 
     plugins: ["dagger2-compiler-2.19"],
 }
 
+filegroup {
+    name: "SystemUI-tests-utils",
+    srcs: [
+        "tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java",
+        "tests/src/com/android/systemui/statusbar/RankingBuilder.java",
+        "tests/src/com/android/systemui/statusbar/SbnBuilder.java",
+    ],
+    path: "tests/src",
+}
+
 android_library {
     name: "SystemUI-tests",
     manifest: "tests/AndroidManifest.xml",
@@ -114,6 +120,7 @@
         "androidx.lifecycle_lifecycle-extensions",
         "androidx.dynamicanimation_dynamicanimation",
         "androidx-constraintlayout_constraintlayout",
+        "iconloader_base",
         "SystemUI-tags",
         "SystemUI-proto",
         "metrics-helper-lib",
@@ -132,7 +139,7 @@
     kotlincflags: ["-Xjvm-default=enable"],
     aaptflags: [
         "--extra-packages",
-        "com.android.keyguard:com.android.systemui",
+        "com.android.systemui",
     ],
     plugins: ["dagger2-compiler-2.19"],
 }
@@ -160,10 +167,6 @@
     kotlincflags: ["-Xjvm-default=enable"],
 
     dxflags: ["--multi-dex"],
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
     required: ["privapp_whitelist_com.android.systemui"],
 
 }
@@ -180,10 +183,6 @@
     privileged: true,
 
     dxflags: ["--multi-dex"],
-    aaptflags: [
-        "--extra-packages",
-        "com.android.keyguard",
-    ],
     optimize: {
         proguard_flags_files: ["proguard.flags", "legacy/recents/proguard.flags"],
     },
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4f74605b..403e894 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -609,6 +609,10 @@
                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                 android:process=":ui"
                 android:visibleToInstantApps="true">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSER" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
         </activity>
 
         <!-- Doze with notifications, run in main sysui process for every user  -->
diff --git a/packages/SystemUI/docs/physics-animation-layout.md b/packages/SystemUI/docs/physics-animation-layout.md
index 488c465..de2ee9e 100644
--- a/packages/SystemUI/docs/physics-animation-layout.md
+++ b/packages/SystemUI/docs/physics-animation-layout.md
@@ -1,7 +1,11 @@
 # Physics Animation Layout
 
 ## Overview
-**PhysicsAnimationLayout** works with an implementation of **PhysicsAnimationController** to construct and maintain physics animations for each of its child views. During the initial construction of the animations, the layout queries the controller for configuration settings such as which properties to animate, which animations to chain together, and what stiffness or bounciness to use. Once the animations are built to the controller’s specifications, the controller can then ask the layout to start, stop and manipulate them arbitrarily to achieve any desired animation effect. The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
+**PhysicsAnimationLayout** works with implementations of **PhysicsAnimationController** to configure and run physics-based animations for each of its child views. During the initial construction of the animations, the layout queries the controller for basic configuration settings such as which properties to animate, which animations to chain together, and the default physics parameters to use.
+
+Once the animations are built, the controller can access **PhysicsPropertyAnimator** instances to run them. The animator behaves similarly to the familiar `ViewPropertyAnimator`, with the ability to animate `alpha`, `translation`, and `scale` values. It also supports additional functionality such as `followAnimatedTargetAlongPath` for more advanced motion.
+
+The controller is notified whenever children are added or removed from the layout, so that it can animate their entrance or exit, respectively.
 
 An example usage is Bubbles, which uses a PhysicsAnimationLayout for its stack of bubbles. Bubbles has controller subclasses including StackAnimationController and ExpansionAnimationController. StackAnimationController tells the layout to configure the translation animations to be chained (for the ‘following’ drag effect), and has methods such as ```moveStack(x, y)``` to animate the stack to a given point. ExpansionAnimationController asks for no animations to be chained, and exposes methods like ```expandStack()``` and ```collapseStack()```, which animate the bubbles to positions along the bottom of the screen.
 
@@ -27,7 +31,7 @@
 ### Animation Control Methods
 Once the layout has used the controller’s configuration properties to build the animations, the controller can use them to actually run animations. This is done for two reasons - reacting to a view being added or removed, or responding to another class (such as a touch handler or broadcast receiver) requesting an animation. ```onChildAdded```, ```onChildRemoved```, and ```setChildVisibility``` are called automatically by the layout, giving the controller the opportunity to animate the child in/out/visible/gone. Custom methods are called by anyone with access to the controller instance to do things like expand, collapse, or move the child views.
 
-In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`. 
+In either case, the controller can use `super.animationForChild` to retrieve a `PhysicsPropertyAnimator` instance. This object behaves similarly to the `ViewPropertyAnimator` object you would receive from `View.animate()`.
 
 #### PhysicsPropertyAnimator
 
@@ -36,9 +40,14 @@
 - `translationX/Y/Z(float)`
 - `scaleX/Y(float)`
 
+...as well as shortcut methods to reduce the amount of boilerplate code needed for common use cases:
+- `position(float, float, Runnable…)`, which starts translationX and translationY animations, and calls the provided callbacks only when both animations have completed.
+- `followAnimatedTargetAlongPath(Path, int, TimeInterpolator)`, which animates a ‘target’ point along the given path using a traditional Animator. As the target moves, the translationX/Y physics animations are updated to follow the target, similarly to how they might follow a touch event location. This results in the view roughly following the path, but with natural motion that takes momentum into account. For example, if a path makes a 90 degree turn to the right, the physics animations will cause the view to curve naturally towards the new trajectory.
+
 It also provides the following configuration methods:
 - `withStartDelay(int)`, for starting the animation after a given delay.
 - `withStartVelocity(float)`, for starting the animation with the given start velocity.
+- `withStiffness(float)` and `withDampingRatio(float)`, for overriding the default physics param values returned by the controller’s getSpringForce method.
 - `withPositionStartVelocities(float, float)`, for setting specific start velocities for TRANSLATION_X and TRANSLATION_Y, since these typically differ.
 - `start(Runnable)`, to start the animation, with an optional end action to call when the animations for every property (including chained animations) have completed.
 
@@ -61,8 +70,7 @@
 
 - Often, animations will set starting values for properties before the animation begins. Property methods like `translationX` have an overloaded variant: `translationX(from, to)`. When `start()` is called, the animation will set the view's translationX property to `from` before beginning the animation to `to`.
 - We may want to use different end actions for each property. For example, if we're animating a view to the bottom of the screen, and also fading it out, we might want to perform an action as soon as the fade out is complete. We can use `alpha(to, endAction)`, which will call endAction as soon as the alpha animation is finished. A special case is `position(x, y, endAction)`, where the endAction is called when both translationX and translationY animations have completed.
-
-`PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
+- `PhysicsAnimationController` also provides `animationsForChildrenFromIndex(int, ChildAnimationConfigurator)`. This is a convenience method for starting animations on multiple child views, starting at the given index. The `ChildAnimationConfigurator` is called with a `PhysicsPropertyAnimator` for each child, where calls to methods like `translationX` and `withStartVelocity` can be made. `animationsForChildrenFromIndex` returns a `MultiAnimationStarter` with a single method, `startAll(endAction)`, which starts all of the animations and calls the end action when they have all completed.
 
 ##### Examples
 Spring the stack of bubbles (whose animations are chained) to the bottom of the screen, shrinking them to 50% size. Once the first bubble is done shrinking, begin fading them out, and then remove them all from the parent once all bubbles have faded out:
@@ -93,16 +101,22 @@
     .startAll(removeFirstView);
 ```
 
+Move a view up along the left side of the screen, and then to the top right of the screen (assume a view that is currently halfway down the left side of the screen). When the translation animations have finished following the target, call a callback:
+
+```
+Path path = new Path();
+path.moveTo(view.getTranslationX(), view.getTranslationY());
+path.lineTo(view.getTranslationX(), 0);
+path.lineTo(mScreenWidth, 0);
+animationForChild(view)
+    .followAnimatedTargetAlongPath(path, 100, new LinearInterpolator())
+    .start(callbackAfterFollowingFinished);
+```
+
 ## PhysicsAnimationLayout
 The layout itself is a FrameLayout descendant with a few extra methods:
 
-```setController(PhysicsAnimationController controller)```
-Attaches the layout to the controller, so that the controller can access the layout’s protected methods. It also constructs or reconfigures the physics animations according to the new controller’s configuration methods.
+```setActiveController(PhysicsAnimationController controller)```
+Sets the given controller as the active controller for the layout. This causes the layout to construct or reconfigure the physics animations according to the new controller’s configuration methods, and halt any in-progress animations.
 
-```setEndListenerForProperty(ViewProperty property, AnimationEndListener endListener)```
-Sets an end listener that is called when all animations on the given property have ended.
-
-```setMaxRenderedChildren(int max)```
-Child views beyond this limit will be set to GONE, and won't be animated, for performance reasons. Defaults to **5**.
-
-It has one protected method, ```animateValueForChildAtIndex(ViewProperty property, int index, float value)```, which is visible to PhysicsAnimationController descendants. This method dispatches the given value to the appropriate animation.
\ No newline at end of file
+Only the currently active controller is allowed to start animations. If a different controller is set as the active controller, the previous controller will no longer be able to start animations. Attempts to do so will have no effect. This is to ensure that multiple controllers aren’t updating animations at the same time, which can cause undefined behavior.
\ No newline at end of file
diff --git a/packages/SystemUI/docs/plugin_hooks.md b/packages/SystemUI/docs/plugin_hooks.md
index 9fe2e18..2fb0c99 100644
--- a/packages/SystemUI/docs/plugin_hooks.md
+++ b/packages/SystemUI/docs/plugin_hooks.md
@@ -56,6 +56,11 @@
 
 Use: Allows replacement of the keyguard main clock.
 
+### Action: com.android.systemui.action.PLUGIN_NPV
+Expected interface: [NPVPlugin](/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java)
+
+Use: Attach a view under QQS for prototyping.
+
 # Global plugin dependencies
 These classes can be accessed by any plugin using PluginDependency as long as they @Requires them.
 
diff --git a/packages/SystemUI/legacy/recents/res/values-vi/strings.xml b/packages/SystemUI/legacy/recents/res/values-vi/strings.xml
index f672a3d..aefeae9 100644
--- a/packages/SystemUI/legacy/recents/res/values-vi/strings.xml
+++ b/packages/SystemUI/legacy/recents/res/values-vi/strings.xml
@@ -34,7 +34,7 @@
     <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_horizontal" msgid="488987777874979435">"Chia 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>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java
new file mode 100644
index 0000000..1426266
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/NPVPlugin.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Plugin to attach custom views under QQS.
+ *
+ * A parent view is provided to the plugin to which they can add Views.
+ * <br>
+ * The parent is a {@link FrameLayout} with same background as QS and 96dp height.
+ *
+ * {@see NPVPluginManager}
+ * {@see status_bar_expanded_plugin_frame}
+ */
+@ProvidesInterface(action = NPVPlugin.ACTION, version = NPVPlugin.VERSION)
+public interface NPVPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_NPV";
+    int VERSION = 1;
+
+    /**
+     * Attach views to the parent.
+     *
+     * @param parent a {@link FrameLayout} to which to attach views. Preferably a root view.
+     * @return a view attached to parent.
+     */
+    View attachToRoot(FrameLayout parent);
+
+    /**
+     * Indicate to the plugin when it is listening (QS expanded)
+     * @param listening
+     */
+    default void setListening(boolean listening) {};
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
index 60435d0..d62c1d4 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/SensorManagerPlugin.java
@@ -22,7 +22,7 @@
 
 /**
  * Allows for additional sensors to be retrieved from
- * {@link com.android.systemui.util.AsyncSensorManager}.
+ * {@link com.android.systemui.util.sensors.AsyncSensorManager}.
  */
 @ProvidesInterface(action = SensorManagerPlugin.ACTION, version = SensorManagerPlugin.VERSION)
 public interface SensorManagerPlugin extends Plugin {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 834f4fc..db026ca 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.plugins.qs;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.metrics.LogMaker;
@@ -25,6 +28,7 @@
 import com.android.systemui.plugins.qs.QSTile.Icon;
 import com.android.systemui.plugins.qs.QSTile.State;
 
+import java.lang.annotation.Retention;
 import java.util.Objects;
 import java.util.function.Supplier;
 
@@ -64,6 +68,14 @@
 
     void destroy();
 
+    /**
+      * return true if the tile supports detail views, and not
+      * only boolean states
+      */
+    default boolean supportsDetailView() {
+        return false;
+    }
+
     CharSequence getTileLabel();
 
     State getState();
@@ -72,6 +84,17 @@
         return logMaker;
     }
 
+    @Retention(SOURCE)
+    @IntDef({COLOR_TILE_ACCENT, COLOR_TILE_RED, COLOR_TILE_BLUE, COLOR_TILE_YELLOW,
+            COLOR_TILE_GREEN})
+    @interface ColorTile {}
+    int COLOR_TILE_ACCENT = 0;
+    int COLOR_TILE_RED = 1;
+    int COLOR_TILE_BLUE = 2;
+    int COLOR_TILE_YELLOW = 3;
+    int COLOR_TILE_GREEN = 4;
+    default void setColor(@ColorTile int color) {}
+
     @ProvidesInterface(version = Callback.VERSION)
     public interface Callback {
         public static final int VERSION = 1;
@@ -118,6 +141,7 @@
         public SlashState slash;
         public boolean handlesLongClick = true;
         public boolean showRippleEffect = true;
+        public int colorActive = -1;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
@@ -137,7 +161,8 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash)
                     || !Objects.equals(other.handlesLongClick, handlesLongClick)
-                    || !Objects.equals(other.showRippleEffect, showRippleEffect);
+                    || !Objects.equals(other.showRippleEffect, showRippleEffect)
+                    || !Objects.equals(other.colorActive, colorActive);
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
@@ -152,6 +177,7 @@
             other.slash = slash != null ? slash.copy() : null;
             other.handlesLongClick = handlesLongClick;
             other.showRippleEffect = showRippleEffect;
+            other.colorActive = colorActive;
             return changed;
         }
 
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 7e03a5c..b6cac13 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -117,7 +117,7 @@
     <string name="kg_pin_accepted" msgid="7637293533973802143">"Kood on õige."</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"Teenus puudub."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"Vaheta sisestusmeetodit"</string>
-    <string name="airplane_mode" msgid="3807209033737676010">"Lennurežiim"</string>
+    <string name="airplane_mode" msgid="3807209033737676010">"Lennukirežiim"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="7246972020562621506">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="6303592361322290145">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
     <string name="kg_prompt_reason_restart_password" msgid="6984641181515902406">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
deleted file mode 100644
index 0c6d57d..0000000
--- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-    <solid android:color="?android:attr/colorBackgroundFloating" />
-    <corners
-        android:topLeftRadius="@dimen/biometric_dialog_corner_size"
-        android:topRightRadius="@dimen/biometric_dialog_corner_size"
-        android:bottomLeftRadius="@dimen/biometric_dialog_corner_size"
-        android:bottomRightRadius="@dimen/biometric_dialog_corner_size"/>
-</shape>
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
index 1661bb2..8c7e82f 100644
--- a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
@@ -24,4 +24,5 @@
         android:width="1dp"
         android:color="#66FFFFFF" />
 
+    <solid android:color="#B3000000" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
new file mode 100644
index 0000000..a1006a8
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -0,0 +1,118 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+        android:id="@+id/title"
+        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="24dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="20sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:paddingHorizontal="24dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="8dp"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:textSize="16sp"
+        android:textColor="?android:attr/textColorPrimary"/>
+
+    <ImageView
+        android:id="@+id/biometric_icon"
+        android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
+        android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+        android:paddingTop="48dp"
+        android:layout_gravity="center_horizontal"
+        android:scaleType="fitXY" />
+
+    <TextView
+        android:id="@+id/indicator"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="24dp"
+        android:paddingTop="16dp"
+        android:paddingBottom="24dp"
+        android:textSize="12sp"
+        android:gravity="center_horizontal"
+        android:accessibilityLiveRegion="polite"
+        android:textColor="@color/biometric_dialog_gray"/>
+
+    <LinearLayout
+        android:id="@+id/button_bar"
+        android:layout_width="match_parent"
+        android:layout_height="72dip"
+        android:paddingTop="24dp"
+        android:layout_gravity="center_vertical"
+        style="?android:attr/buttonBarStyle"
+        android:orientation="horizontal">
+        <Space android:id="@+id/leftSpacer"
+            android:layout_width="12dp"
+            android:layout_height="match_parent"
+            android:visibility="visible" />
+        <!-- Negative Button -->
+        <Button android:id="@+id/button_negative"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+            android:gravity="center"
+            android:maxLines="2"/>
+        <Space android:id="@+id/middleSpacer"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:visibility="visible"/>
+        <!-- Positive Button -->
+        <Button android:id="@+id/button_positive"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Colored"
+            android:gravity="center"
+            android:maxLines="2"
+            android:text="@string/biometric_dialog_confirm"
+            android:visibility="gone"/>
+        <!-- Try Again Button -->
+        <Button android:id="@+id/button_try_again"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            style="@*android:style/Widget.DeviceDefault.Button.Colored"
+            android:gravity="center"
+            android:maxLines="2"
+            android:text="@string/biometric_dialog_try_again"
+            android:visibility="gone"/>
+        <Space android:id="@+id/rightSpacer"
+            android:layout_width="12dip"
+            android:layout_height="match_parent"
+            android:visibility="visible" />
+    </LinearLayout>
+
+</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
new file mode 100644
index 0000000..be30f21
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.systemui.biometrics.AuthBiometricFaceView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contents"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <include layout="@layout/auth_biometric_contents"/>
+
+</com.android.systemui.biometrics.AuthBiometricFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
new file mode 100644
index 0000000..ce53e27
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<com.android.systemui.biometrics.AuthBiometricFingerprintView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contents"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <include layout="@layout/auth_biometric_contents"/>
+
+</com.android.systemui.biometrics.AuthBiometricFingerprintView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml
new file mode 100644
index 0000000..23199aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_container_view.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@color/biometric_dialog_dim_color"
+        android:contentDescription="@string/biometric_dialog_empty_space_description"/>
+
+    <View
+        android:id="@+id/panel"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="?android:attr/colorBackgroundFloating"
+        android:elevation="@dimen/biometric_dialog_elevation"/>
+
+    <ScrollView
+        android:id="@+id/scrollview"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_margin="@dimen/biometric_dialog_border_padding"
+        android:elevation="@dimen/biometric_dialog_elevation"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
deleted file mode 100644
index e687cdf..0000000
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ /dev/null
@@ -1,190 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/layout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <ImageView
-        android:id="@+id/background"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:scaleType="center" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="bottom"
-        android:background="@color/biometric_dialog_dim_color"
-        android:orientation="vertical">
-
-        <!-- This is not a Space since Spaces cannot be clicked -->
-        <View
-            android:id="@+id/space"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-        <ScrollView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
-                depending on horizontal/portrait orientation -->
-                <View
-                    android:id="@+id/left_space"
-                    android:layout_weight="1"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-                    <LinearLayout
-                        android:id="@+id/dialog"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:orientation="vertical"
-                        android:background="@drawable/biometric_dialog_bg"
-                        android:layout_marginBottom="@dimen/biometric_dialog_border_padding"
-                        android:layout_marginLeft="@dimen/biometric_dialog_border_padding"
-                        android:layout_marginRight="@dimen/biometric_dialog_border_padding">
-
-                        <TextView
-                            android:id="@+id/title"
-                            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:layout_marginTop="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:textSize="20sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <TextView
-                            android:id="@+id/subtitle"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="8dp"
-                            android:layout_marginStart="24dp"
-                            android:layout_marginEnd="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:textSize="16sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <TextView
-                            android:id="@+id/description"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:gravity="@integer/biometric_dialog_text_gravity"
-                            android:paddingTop="8dp"
-                            android:textSize="16sp"
-                            android:textColor="?android:attr/textColorPrimary"/>
-
-                        <ImageView
-                            android:id="@+id/biometric_icon"
-                            android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
-                            android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
-                            android:layout_gravity="center_horizontal"
-                            android:layout_marginTop="48dp"
-                            android:scaleType="fitXY" />
-
-                        <TextView
-                            android:id="@+id/error"
-                            android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
-                            android:layout_marginEnd="24dp"
-                            android:layout_marginStart="24dp"
-                            android:paddingTop="16dp"
-                            android:paddingBottom="24dp"
-                            android:textSize="12sp"
-                            android:gravity="center_horizontal"
-                            android:accessibilityLiveRegion="polite"
-                            android:textColor="@color/biometric_dialog_gray"/>
-
-                        <LinearLayout
-                            android:layout_width="match_parent"
-                            android:layout_height="72dip"
-                            android:paddingTop="24dp"
-                            android:layout_gravity="center_vertical"
-                            style="?android:attr/buttonBarStyle"
-                            android:orientation="horizontal"
-                            android:measureWithLargestChild="true">
-                            <Space android:id="@+id/leftSpacer"
-                                android:layout_width="12dp"
-                                android:layout_height="match_parent"
-                                android:visibility="visible" />
-                            <!-- Negative Button -->
-                            <Button android:id="@+id/button2"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                                android:gravity="center"
-                                android:maxLines="2" />
-                            <Space android:id="@+id/middleSpacer"
-                                android:layout_width="0dp"
-                                android:layout_height="match_parent"
-                                android:layout_weight="1"
-                                android:visibility="visible" />
-                            <!-- Positive Button -->
-                            <Button android:id="@+id/button1"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Colored"
-                                android:gravity="center"
-                                android:maxLines="2"
-                                android:text="@string/biometric_dialog_confirm"
-                                android:visibility="gone"/>
-                            <!-- Try Again Button -->
-                            <Button android:id="@+id/button_try_again"
-                                android:layout_width="wrap_content"
-                                android:layout_height="match_parent"
-                                style="@*android:style/Widget.DeviceDefault.Button.Colored"
-                                android:gravity="center"
-                                android:maxLines="2"
-                                android:text="@string/biometric_dialog_try_again"
-                                android:visibility="gone"/>
-                            <Space android:id="@+id/rightSpacer"
-                                android:layout_width="12dip"
-                                android:layout_height="match_parent"
-                                android:visibility="visible" />
-                        </LinearLayout>
-                    </LinearLayout>
-
-                <!-- This is not a Space since Spaces cannot be clicked. The width of this changes
-                depending on horizontal/portrait orientation -->
-                <View
-                    android:id="@+id/right_space"
-                    android:layout_weight="1"
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:contentDescription="@string/biometric_dialog_empty_space_description"/>
-
-            </LinearLayout>
-
-        </ScrollView>
-
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index 245177c..ca085b6 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -20,6 +20,13 @@
     android:layout_height="@dimen/pip_dismiss_gradient_height"
     android:layout_gravity="bottom|center_horizontal">
 
+    <FrameLayout
+        android:id="@+id/bubble_dismiss_circle"
+        android:layout_width="@dimen/bubble_dismiss_encircle_size"
+        android:layout_height="@dimen/bubble_dismiss_encircle_size"
+        android:layout_gravity="center"
+        android:background="@drawable/bubble_dismiss_circle" />
+
     <LinearLayout
         android:id="@+id/bubble_dismiss_icon_container"
         android:layout_width="wrap_content"
@@ -38,29 +45,5 @@
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:src="@drawable/bubble_dismiss_icon" />
-
-        <TextView
-            android:id="@+id/bubble_dismiss_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="9dp"
-            android:layout_marginBottom="9dp"
-            android:layout_marginLeft="8dp"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
-            android:textColor="@android:color/white"
-            android:shadowColor="@android:color/black"
-            android:shadowDx="-1"
-            android:shadowDy="1"
-            android:shadowRadius="0.01"
-            android:text="@string/bubble_dismiss_text" />
-
     </LinearLayout>
-
-    <FrameLayout
-        android:id="@+id/bubble_dismiss_circle"
-        android:layout_width="@dimen/bubble_dismiss_encircle_size"
-        android:layout_height="@dimen/bubble_dismiss_encircle_size"
-        android:layout_gravity="center"
-        android:alpha="0"
-        android:background="@drawable/bubble_dismiss_circle" />
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index a8eb2914..e2dea45 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -24,7 +24,6 @@
         android:id="@+id/bubble_image"
         android:layout_width="@dimen/individual_bubble_size"
         android:layout_height="@dimen/individual_bubble_size"
-        android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
 </com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml b/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
index 2b2100c..9376b1f 100644
--- a/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
+++ b/packages/SystemUI/res/layout/keyguard_indication_text_view.xml
@@ -1,17 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
-        android:id="@+id/keyguard_indication_enterprise_disclosure"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:paddingStart="@dimen/keyguard_indication_text_padding"
-        android:paddingEnd="@dimen/keyguard_indication_text_padding"
-        android:textAppearance="@style/TextAppearance.Keyguard.BottomArea"
-        android:visibility="gone"/>
-
     <com.android.systemui.statusbar.phone.KeyguardIndicationTextView
         android:id="@+id/keyguard_indication_text"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/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/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 7a94008..e7c6b25 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -111,4 +111,6 @@
         android:visibility="invisible"
         android:background="@drawable/qs_navbar_scrim" />
 
+    <include layout="@layout/status_bar_expanded_plugin_frame"/>
+
 </com.android.systemui.statusbar.phone.NotificationPanelView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
new file mode 100644
index 0000000..4849dfb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_expanded_plugin_frame.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/plugin_frame"
+    android:theme="@style/qs_theme"
+    android:layout_width="@dimen/qs_panel_width"
+    android:layout_height="96dp"
+    android:layout_gravity="center_horizontal"
+    android:layout_marginTop="@*android:dimen/quick_qs_total_height"
+    android:layout_marginLeft="@dimen/notification_side_paddings"
+    android:layout_marginRight="@dimen/notification_side_paddings"
+    android:visibility="gone"
+    android:background="@drawable/qs_background_primary"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index a914930..9716a00 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -44,7 +44,7 @@
     </com.android.systemui.statusbar.BackDropView>
 
     <com.android.systemui.statusbar.ScrimView
-        android:id="@+id/scrim_behind"
+        android:id="@+id/scrim_for_bubble"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:importantForAccessibility="no"
@@ -56,6 +56,14 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
+    <com.android.systemui.statusbar.ScrimView
+        android:id="@+id/scrim_behind"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:importantForAccessibility="no"
+        sysui:ignoreRightInset="true"
+        />
+
     <include layout="@layout/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 538fdce..4d451bc 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Kanselleer"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bevestig"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Probeer weer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Leë gebied; tik om stawing te kanselleer"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tik om stawing te kanselleer"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probeer asseblief weer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Soek tans jou gesig"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gesig is gestaaf"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bevestig"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tik op Bevestig om te voltooi"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Gestaaf"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Raak die vingerafdruksensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Vingerafdrukikoon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Soek tans vir jou …"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index a3ff5b4..4d0a62e 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ይቅር"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"አረጋግጥ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"እንደገና ይሞክሩ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ባዶ ክልል፣ ፈቃድን ለመሰረዝ መታ ያድርጉ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ማረጋገጥን ለመሰረዝ መታ ያድርጉ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"እባክዎ እንደገና ይሞክሩ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"መልክዎን በመፈለግ ላይ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"መልክ ተረጋግጧል"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ተረጋግጧል"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ለማጠናቀቅ አረጋግጥን መታ ያድርጉ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"የተረጋገጠ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"የጣት አሻራ ዳሳሹን ይንኩ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"የጣት አሻራ አዶ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"እርስዎን በመፈለግ ላይ…"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 529ca5c..787fc8d 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"إلغاء"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تأكيد"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"إعادة المحاولة"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"منطقة خالية، يُرجى النقر لإلغاء المصادقة."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"انقر لإلغاء المصادقة."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"يُرجى إعادة المحاولة."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"جارٍ البحث عن وجهك"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"تمّت مصادقة الوجه."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تمّ التأكيد."</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"يمكنك النقر على \"تأكيد\" لإكمال المهمة."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"مصادقة"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"المس زر استشعار بصمة الإصبع"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"رمز بصمة الإصبع"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"جارٍ البحث عن وجهك…"</string>
@@ -555,8 +556,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. مرّر الشاشة بسرعة للأعلى مع الاستمرار لإزالة تثبيت الشاشة."</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"نظرة عامة\" لإزالة التثبيت."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"يؤدي هذا إلى استمرار عرض الشاشة المُختارة إلى أن تتم إزالة تثبيتها. المس مع الاستمرار زر \"الشاشة الرئيسية\" لإزالة التثبيت."</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"لإزالة تثبيت هذه الشاشة، يمكنك أن تلمس مع الاستمرار زرّي \"رجوع\" و\"نظرة عامة\"."</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"لإزالة تثبيت هذه الشاشة، يمكنك أن تلمس مع الاستمرار زرّي \"رجوع\" و\"الشاشة الرئيسية\"."</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"لإزالة تثبيت هذه الشاشة، يمكنك النقر مع الاستمرار على زرّي \"الرجوع\" و\"النظرة العامة\"."</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"لإزالة تثبيت هذه الشاشة، يمكنك النقر مع الاستمرار على زرّي \"الرجوع\" و\"الشاشة الرئيسية\"."</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"مرّر الشاشة بسرعة للأعلى مع الاستمرار لإزالة تثبيت الشاشة."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"حسنًا"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"لا، شكرًا"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 7d8ba64..d7feb0c 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"বাতিল কৰক"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"নিশ্চিত কৰক"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"আকৌ চেষ্টা কৰক"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"এলেকা খালী আছে, বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ বাতিল কৰিবলৈ টিপক"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ বাতিল কৰিবলৈ টিপক"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"অনুগ্ৰহ কৰি আকৌ চেষ্টা কৰক"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"আপোনাৰ মুখমণ্ডল বিচাৰি থকা হৈছে"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"মুখমণ্ডলৰ বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰা হ’ল"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"নিশ্চিত কৰিলে"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"সম্পূৰ্ণ কৰিবলৈ নিশ্চিত কৰক-ত টিপক"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"বিশ্বাসযোগ্যতা প্ৰমাণীকৰণ কৰা হ’ল"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ফিংগাৰপ্ৰিণ্ট ছেন্সৰটো স্পৰ্শ কৰক"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ফিংগাৰপ্ৰিণ্ট আইকন"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"আপোনাৰ মুখমণ্ডল বিচাৰি আছে…"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 5ae4d2d..f6cd5dc 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ləğv et"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Təsdiq"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Yenidən cəhd edin"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Region boşdur, identifikasiyanı ləğv etmək üçün toxunun"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Doğrulanmanı ləğv etmək üçün toxunun"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Yenidən cəhd edin"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Üzünüz axtarılır"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Üz doğrulandı"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Təsdiqləndi"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tamamlamaq üçün \"Təsdiq edin\" seçiminə toxunun"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Doğrulandı"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Barmaq izi sensoruna klikləyin"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Barmaq izi ikonası"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Siz axtarılırsınız…"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 95346e4..2196c15 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Otkaži"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Probaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazna oblast, dodirnite da biste otkazali potvrdu identiteta"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da biste otkazali potvrdu identiteta"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traži se vaše lice"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je potvrđeno"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi da biste završili"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Identitet je potvrđen"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor za otisak prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otiska prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3674564..06ee3e1 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Скасаваць"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Пацвердзіць"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Паўтарыць спробу"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Пустая вобласць. Націсніце, каб скасаваць аўтэнтыфікацыю"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Націсніце, каб скасаваць аўтэнтыфікацыю"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Паўтарыце спробу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ідзе пошук твару"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Твар распазнаны"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Пацверджана"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Націсніце \"Пацвердзіць\", каб завяршыць"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Распазнана"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Дакраніцеся да сканера адбіткаў пальцаў"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок адбіткаў пальцаў"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ідзе пошук вашага твару…"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1618e05..6f37a75 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Отказ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потвърждаване"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Нов опит"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празна област. Докоснете, за да анулирате удостоверяването"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Докоснете, за да анулирате удостоверяването"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Моля, опитайте отново"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Лицето ви се търси"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицето е удостоверено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потвърдено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Докоснете „Потвърждаване“ за завършване"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Удостоверено"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Докоснете сензора за отпечатъци"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона за отпечатък"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Търсим ви…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Ппоказване на по-малко спешните известия по-долу"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Докоснете отново, за да отворите"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Прекарайте пръст нагоре, за да отключите"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Прекарайте пръст нагоре, за да опитате отново"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Плъзнете бързо нагоре, за да опитате отново"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Това устройство се управлява от организацията ви"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Това устройство се управлява от <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Плъзнете с пръст от иконата, за да използвате телефона"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 6b7561b..9c33f2b 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"বাতিল করুন"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"কনফার্ম করুন"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"আবার চেষ্টা করুন"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"জায়গাটি খালি, যাচাইকরণ বাতিল করতে এখানে ট্যাপ করুন"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"যাচাইকরণ বাতিল করতে ট্যাপ করুন"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"আবার চেষ্টা করুন"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"আপনার ফেস খোঁজা হচ্ছে"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ফেস যাচাই করা হয়েছে"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"কনফার্ম করা হয়েছে"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"সম্পূর্ণ করতে \'কনফার্ম করুন\' বোতামে ট্যাপ করুন"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"প্রমাণীকৃত"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"আঙ্গুলের ছাপের সেন্সর স্পর্শ করুন"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"আঙ্গুলের ছাপের আইকন"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"আপনার জন্য খোঁজা হচ্ছে…"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index c22ddfd..2d77626 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Otkaži"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Pokušaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Područje je prazno. Dodirnite da otkažete autentifikaciju."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da otkažete autentifikaciju"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pokušajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traženje vašeg lica"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je provjereno"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi da završite"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentificirano"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor za otisak prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona za otisak prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index d4147ba..ee16c58 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel·la"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirma"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Torna-ho a provar"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zona buida; toca per cancel·lar l\'autenticació"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca per cancel·lar l\'autenticació"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Torna-ho a provar"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"S\'està cercant la teva cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Cara autenticada"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirma per completar"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticat"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor d\'empremtes digitals"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona d\'empremta digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"S\'està cercant la teva cara…"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 87aaa94..bed63db 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Zrušit"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdit"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Zkusit znovu"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prázdná oblast, klepnutím ověření zrušíte"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Klepnutím zrušíte ověření"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Zkuste to prosím znovu"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Vyhledávání obličeje"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Obličej byl ověřen"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrzeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ověření dokončíte klepnutím na Potvrdit"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Ověřeno"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotkněte se snímače otisků prstů"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otisku prstu"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hledáme vás…"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 6a97ee6..45ef5b1 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuller"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekræft"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Prøv igen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tomt område. Tryk for at annullere godkendelsen."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tryk for at annullere godkendelsen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Prøv igen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Søger efter dit ansigt"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansigtet er godkendt"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekræftet"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tryk på Bekræft for at udføre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Godkendt"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sæt fingeren på fingeraftrykslæseren"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon for fingeraftryk"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Forsøger at finde dig…"</string>
@@ -215,7 +216,7 @@
     <skip />
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notifikationen er annulleret."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Notifikationspanel."</string>
-    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hurtige indstillinger."</string>
+    <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Kvikmenu."</string>
     <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Låseskærm."</string>
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"Indstillinger"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Oversigt."</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Dette fastholder skærmen i visningen, indtil du frigør den. Tryk på Tilbage, og hold fingeren nede for at frigøre skærmen."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Dette fastholder skærmen i visningen, indtil du frigør den. Hold Startskærm nede for at frigøre skærmen."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Hold knapperne Tilbage og Oversigt nede for at frigøre skærmen"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Hold knapperne Tilbage og Startskærm nede for at frigøre skærmen"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Hold knapperne Tilbage og Hjem nede for at frigøre skærmen"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Stryg opad, og hold fingeren nede for at frigøre denne skærm"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"OK, det er forstået"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Nej tak"</string>
@@ -588,7 +589,7 @@
     <string name="system_ui_tuner" msgid="708224127392452018">"System UI Tuner"</string>
     <string name="show_battery_percentage" msgid="5444136600512968798">"Vis procent for det indbyggede batteri"</string>
     <string name="show_battery_percentage_summary" msgid="3215025775576786037">"Vis procenttallet for batteriniveauet i ikonet for statusbjælken, når der ikke oplades"</string>
-    <string name="quick_settings" msgid="10042998191725428">"Hurtige indstillinger"</string>
+    <string name="quick_settings" msgid="10042998191725428">"Kvikmenu"</string>
     <string name="status_bar" msgid="4877645476959324760">"Statusbjælke"</string>
     <string name="overview" msgid="4018602013895926956">"Oversigt"</string>
     <string name="demo_mode" msgid="2532177350215638026">"Demotilstand for systemets brugerflade"</string>
@@ -604,7 +605,7 @@
     <string name="zen_alarm_warning" msgid="444533119582244293">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template" msgid="3980063409350522735">"kl. <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template_far" msgid="4242179982586714810">"på <xliff:g id="WHEN">%1$s</xliff:g>"</string>
-    <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Hurtige indstillinger <xliff:g id="TITLE">%s</xliff:g>."</string>
+    <string name="accessibility_quick_settings_detail" msgid="2579369091672902101">"Kvikmenu <xliff:g id="TITLE">%s</xliff:g>."</string>
     <string name="accessibility_status_bar_hotspot" msgid="4099381329956402865">"Hotspot"</string>
     <string name="accessibility_managed_profile" msgid="6613641363112584120">"Arbejdsprofil"</string>
     <string name="tuner_warning_title" msgid="7094689930793031682">"Sjovt for nogle, men ikke for alle"</string>
@@ -617,8 +618,8 @@
     <string name="activity_not_found" msgid="348423244327799974">"Applikationen er ikke installeret på din enhed."</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Vis sekunder"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Vis sekunder i statuslinjen. Dette kan påvirke batteriets levetid."</string>
-    <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Hurtige indstillinger"</string>
-    <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Hurtige indstillinger"</string>
+    <string name="qs_rearrange" msgid="8060918697551068765">"Omarranger Kvikmenu"</string>
+    <string name="show_brightness" msgid="6613930842805942519">"Vis lysstyrke i Kvikmenu"</string>
     <string name="experimental" msgid="6198182315536726162">"Eksperimentel"</string>
     <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vil du slå Bluetooth til?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"Bluetooth skal være slået til, før du kan knytte dit tastatur til din tablet."</string>
@@ -813,15 +814,15 @@
     <string name="accessibility_qs_edit_remove_tile" msgid="7484493384665907197">"Fjern <xliff:g id="TILE_NAME">%1$s</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_add" msgid="3520406665865985109">"Føj <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_move" msgid="3108103090006972938">"Flyt <xliff:g id="TILE_NAME">%1$s</xliff:g> til position <xliff:g id="POSITION">%2$d</xliff:g>"</string>
-    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj for Hurtige indstillinger."</string>
+    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Redigeringsværktøj til Kvikmenu."</string>
     <string name="accessibility_desc_notification_icon" msgid="8352414185263916335">"<xliff:g id="ID_1">%1$s</xliff:g>-notifikation: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="dock_forced_resizable" msgid="5914261505436217520">"Appen fungerer muligvis ikke i opdelt skærm."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"Appen understøtter ikke opdelt skærm."</string>
     <string name="forced_resizable_secondary_display" msgid="4230857851756391925">"Appen fungerer muligvis ikke på sekundære skærme."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="7793821742158306742">"Appen kan ikke åbnes på sekundære skærme."</string>
     <string name="accessibility_quick_settings_settings" msgid="6132460890024942157">"Åbn Indstillinger."</string>
-    <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Hurtige indstillinger."</string>
-    <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Hurtige indstillinger."</string>
+    <string name="accessibility_quick_settings_expand" msgid="2375165227880477530">"Åbn Kvikmenu."</string>
+    <string name="accessibility_quick_settings_collapse" msgid="1792625797142648105">"Luk Kvikmenu."</string>
     <string name="accessibility_quick_settings_alarm_set" msgid="1863000242431528676">"Alarmen er indstillet."</string>
     <string name="accessibility_quick_settings_user" msgid="1567445362870421770">"Logget ind som <xliff:g id="ID_1">%s</xliff:g>"</string>
     <string name="data_connection_no_internet" msgid="4503302451650972989">"Intet internet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index e1fd8db..1deceb1 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Abbrechen"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bestätigen"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Noch einmal versuchen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Feld \"Region\" ist leer, zum Abbrechen der Authentifizierung tippen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Zum Abbrechen der Authentifizierung tippen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Noch einmal versuchen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Nach deinem Gesicht wird gesucht"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gesicht authentifiziert"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bestätigt"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Zum Abschließen auf \"Bestätigen\" tippen"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifiziert"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Berühre den Fingerabdrucksensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerabdruck-Symbol"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Wir suchen nach dir…"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index dfb5edf..2b57ab5 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ακύρωση"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Επιβεβαίωση"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Δοκιμάστε ξανά"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Κενή περιοχή, πατήστε για ακύρωση ελέγχου ταυτότητας"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Πατήστε για ακύρωση του ελέγχου ταυτότητας"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Δοκιμάστε ξανά"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Αναζήτηση για το πρόσωπό σας"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Έγινε έλεγχος ταυτότητας προσώπου"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Επιβεβαιώθηκε"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Πατήστε Επιβεβαίωση για ολοκλήρωση"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Ολοκληρώθηκε ο έλεγχος ταυτότητας"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Αγγίξτε τον αισθητήρα δακτυλικών αποτυπωμάτων"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Εικονίδιο δακτυλικών αποτυπωμάτων"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Αναζήτηση για εσάς…"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 938cb0d..fe19e7c 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 4fd8ed9..7a6271f 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirm"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Try again"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Empty region, tap to cancel authentication"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tap to cancel authentication"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Please try again"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Looking for your face"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Face authenticated"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmed"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tap Confirm to complete"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authenticated"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touch the fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingerprint icon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Looking for you…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"This keeps it in view until you unpin. Touch &amp; hold Overview to unpin."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"This keeps it in view until you unpin. Touch &amp; hold Home to unpin."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"To unpin this screen, touch &amp; hold Back and Overview buttons"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch &amp; hold Back and Home buttons"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"To unpin this screen, touch and hold Back and Home buttons"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"To unpin this screen, swipe up &amp; hold"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Got it"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, thanks"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 139215aa..32469f2 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎Cancel‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Confirm‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‎Try again‎‏‎‎‏‎"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎Empty region, tap to cancel authentication‎‏‎‎‏‎"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎Tap to cancel authentication‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‏‏‎‎Please try again‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‎‎Looking for your face‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎Face authenticated‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎Confirmed‎‏‎‎‏‎"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎Tap Confirm to complete‎‏‎‎‏‎"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎Authenticated‎‏‎‎‏‎"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎Touch the fingerprint sensor‎‏‎‎‏‎"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎Fingerprint icon‎‏‎‎‏‎"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‏‎Looking for you…‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6adf6d5..f29aa1e 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Volver a intentarlo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Espacio vacío; presiona para cancelar la autenticación"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Presiona para cancelar la autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vuelve a intentarlo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando tu rostro"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Se autenticó el rostro"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmado"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Presiona Confirmar para completarla"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor de huellas digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícono de huella digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Autenticando tu rostro…"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index c4f3445..3088098 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Reintentar"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zona vacía. Toca para cancelar la autenticación."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca para cancelar la autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vuelve a intentarlo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando tu cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Cara autenticada"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirmar para completar la acción"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Se ha autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca el sensor de huellas digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icono de huella digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Buscando tu cara…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Aplicaciones recientes."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"La pantalla se mantiene visible hasta que dejas de fijarla. Para ello, mantén pulsado el botón Inicio."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Mantén pulsado el botón Atrás y el de aplicaciones recientes para dejar de fijar esta pantalla"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Mantén pulsado el botón Atrás y el de Inicio para dejar de fijar esta pantalla"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Mantén pulsados los botones Atrás e Inicio para dejar de fijar esta pantalla"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para dejar de fijar esta pantalla, desliza el dedo hacia arriba y mantenla pulsada"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendido"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"No, gracias"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index a8ad1ec..51226ab 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Tühista"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Kinnita"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Proovi uuesti"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tühi piirkond, puudutage autentimise tühistamiseks"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Puudutage autentimise tühistamiseks"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Proovige uuesti"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Teie näo vaatamine"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Nägu on autenditud"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Kinnitatud"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Lõpuleviimiseks puudutage nuppu Kinnita"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenditud"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Puudutage sõrmejäljeandurit"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Sõrmejälje ikoon"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Otsitakse teid …"</string>
@@ -192,7 +193,7 @@
     <string name="not_default_data_content_description" msgid="9194667237765917844">"Ei ole andmeside kasutamiseks seadistatud"</string>
     <string name="cell_data_off" msgid="1051264981229902873">"Väljas"</string>
     <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Jagamine Bluetoothiga."</string>
-    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Lennurežiim."</string>
+    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Lennukirežiim."</string>
     <string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN on sees."</string>
     <string name="accessibility_no_sims" msgid="3957997018324995781">"SIM-kaarti pole."</string>
     <string name="carrier_network_change_mode" msgid="8149202439957837762">"Operaatori võrku muudetakse"</string>
@@ -229,7 +230,7 @@
     <string name="accessibility_quick_settings_airplane_off" msgid="7786329360056634412">"Lennurežiim on väljas."</string>
     <string name="accessibility_quick_settings_airplane_on" msgid="6406141469157599296">"Lennurežiim on sees."</string>
     <string name="accessibility_quick_settings_airplane_changed_off" msgid="66846307818850664">"Lennurežiim on välja lülitatud."</string>
-    <string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"Lennurežiim on sisse lülitatud."</string>
+    <string name="accessibility_quick_settings_airplane_changed_on" msgid="8983005603505087728">"Lennukirežiim on sisse lülitatud."</string>
     <string name="accessibility_quick_settings_dnd_none_on" msgid="2960643943620637020">"täielik vaikus"</string>
     <string name="accessibility_quick_settings_dnd_alarms_on" msgid="3357131899365865386">"ainult alarmid"</string>
     <string name="accessibility_quick_settings_dnd" msgid="5555155552520665891">"Mitte segada."</string>
@@ -597,7 +598,7 @@
     <string name="status_bar_ethernet" msgid="5044290963549500128">"Ethernet"</string>
     <string name="status_bar_alarm" msgid="8536256753575881818">"Äratus"</string>
     <string name="status_bar_work" msgid="6022553324802866373">"Tööprofiil"</string>
-    <string name="status_bar_airplane" msgid="7057575501472249002">"Lennurežiim"</string>
+    <string name="status_bar_airplane" msgid="7057575501472249002">"Lennukirežiim"</string>
     <string name="add_tile" msgid="2995389510240786221">"Paani lisamine"</string>
     <string name="broadcast_tile" msgid="3894036511763289383">"Paani ülekandmine"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Kuulete järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g> vaid siis, kui lülitate selle enne seda välja"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 799d773..0d2863e 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Utzi"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Berretsi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Saiatu berriro"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Hutsik dago eremua. Sakatu autentifikatzeari uzteko."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Sakatu hau autentifikazioa bertan behera uzteko"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Saiatu berriro"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Aurpegia bilatzen"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Autentifikatu da aurpegia"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Berretsita"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Amaitzeko, sakatu \"Berretsi\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikatuta"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sakatu hatz-marken sentsorea"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Hatz-markaren ikonoa"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Zure bila…"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index eeb30a0..e649d80 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -28,15 +28,15 @@
     <string name="battery_low_percent_format" msgid="2900940511201380775">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است"</string>
     <string name="battery_low_percent_format_hybrid" msgid="6838677459286775617">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> باقی مانده، براساس اشتفاده شما حدود <xliff:g id="TIME">%2$s</xliff:g> باقی مانده است"</string>
     <string name="battery_low_percent_format_hybrid_short" msgid="9025795469949145586">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> باقی مانده، حدود <xliff:g id="TIME">%2$s</xliff:g> باقی مانده است"</string>
-    <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. بهینه‌سازی باتری روشن است."</string>
+    <string name="battery_low_percent_format_saver_started" msgid="7879389868952879166">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است. «بهینه‌سازی باتری» روشن است."</string>
     <string name="invalid_charger" msgid="2741987096648693172">"‏ازطریق USB شارژ نمی‌شود. از شارژر ارائه‌شده با دستگاه استفاده کنید."</string>
     <string name="invalid_charger_title" msgid="2836102177577255404">"‏ازطریق USB شارژ نمی‌شود"</string>
     <string name="invalid_charger_text" msgid="6480624964117840005">"از شارژر ارائه‌شده با دستگاه استفاده کنید"</string>
     <string name="battery_low_why" msgid="4553600287639198111">"تنظیمات"</string>
-    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"بهینه‌سازی باتری روشن شود؟"</string>
-    <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"درباره بهینه‌سازی باتری"</string>
+    <string name="battery_saver_confirmation_title" msgid="2052100465684817154">"«بهینه‌سازی باتری» روشن شود؟"</string>
+    <string name="battery_saver_confirmation_title_generic" msgid="2090922638411744540">"درباره «بهینه‌سازی باتری»"</string>
     <string name="battery_saver_confirmation_ok" msgid="7507968430447930257">"روشن کردن"</string>
-    <string name="battery_saver_start_action" msgid="8187820911065797519">"بهینه‌سازی باتری را روشن کنید"</string>
+    <string name="battery_saver_start_action" msgid="8187820911065797519">"«بهینه‌سازی باتری» را روشن کنید"</string>
     <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"تنظیمات"</string>
     <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"Wi-Fi"</string>
     <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"چرخش خودکار صفحه"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"لغو"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تأیید"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"امتحان مجدد"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"برای لغو احراز هویت، در قسمت خالی ضربه بزنید"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"برای لغو راستی‌آزمایی ضربه بزنید"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"لطفاً دوباره امتحان کنید"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"درحال جستجوی چهره"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"چهره احراز هویت شد"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تأیید شد"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"برای تکمیل، روی تأیید ضربه بزنید"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"راستی‌آزمایی‌شده"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"حسگر اثر انگشت را لمس کنید"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"نماد اثر انگشت"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"درحال جستجوی شما…"</string>
@@ -450,9 +451,9 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"کاربر حذف شود؟"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"همه برنامه‌ها و داده‌های این کاربر حذف می‌شود."</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"حذف"</string>
-    <string name="battery_saver_notification_title" msgid="8614079794522291840">"بهینه‌سازی باتری روشن است"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"«بهینه‌سازی باتری» روشن است"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"عملکرد و اطلاعات پس‌زمینه را کاهش می‌دهد"</string>
-    <string name="battery_saver_notification_action_text" msgid="132118784269455533">"بهینه‌سازی باتری را خاموش کنید"</string>
+    <string name="battery_saver_notification_action_text" msgid="132118784269455533">"«بهینه‌سازی باتری» را خاموش کنید"</string>
     <string name="media_projection_dialog_text" msgid="8585357687598538511">"هنگام ضبط یا ارسال محتوا، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> می‌تواند هرگونه اطلاعات حساس را (مانند صوت، گذرواژه، اطلاعات پرداخت، عکس و پیام) که روی صفحه‌تان نشان داده می‌شود یا از دستگاهتان پخش می‌شود ضبط کند."</string>
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"هنگام ضبط یا ارسال محتوا، ارائه‌دهنده خدمات این عملکرد می‌تواند هرگونه اطلاعات حساس (مانند صوت، گذرواژه، اطلاعات پرداخت، عکس و پیام) را که روی صفحه‌تان نشان داده می‌شود یا از دستگاهتان پخش می‌شود ضبط کند."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"افشای اطلاعات حساس درحین ارسال/ضبط محتوا"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 7be438a..e195754 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Peruuta"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Vahvista"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Yritä uudelleen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tyhjä alue, napauta sitä peruuttaaksesi tunnistuksen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Peruuta todennus napauttamalla"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Yritä uudelleen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Kasvojasi katsotaan"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Kasvot tunnistettu"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Vahvistettu"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Valitse lopuksi Vahvista"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Todennettu"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Kosketa sormenjälkitunnistinta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Sormenjälkikuvake"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Etsitään kasvoja…"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index e8e4232..4f28efa 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuler"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmer"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Réessayer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Région vide, touchez l\'écran pour annuler l\'authentification"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Touchez ici pour annuler l\'authentification"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Veuillez réessayer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"L\'appareil recherche votre visage…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Visage authentifié"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmé"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Touchez Confirmer pour terminer"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifié"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Touchez le capteur d\'empreintes digitales"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icône d\'empreinte digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Recherche de votre visage…"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 3bc9378..a51ebb1 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuler"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmer"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Réessayer"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zone vide : appuyez pour annuler l\'authentification"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Appuyer pour annuler l\'authentification"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Veuillez réessayer"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Recherche de votre visage…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Visage authentifié"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmé"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Appuyez sur \"Confirmer\" pour terminer"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Authentifié"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Appuyez sur le lecteur d\'empreinte digitale"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icône d\'empreinte digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Recherche de votre visage…"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 15d9c2b..7a7ebf2 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar de novo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"A rexión está baleira; tócaa para cancelar a autenticación"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toca para cancelar a autenticación"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Téntao de novo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Buscando a túa cara"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Autenticouse a cara"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toca Confirmar para completar o proceso"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toca o sensor de impresión dixital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona de impresión dixital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Buscándote…"</string>
@@ -599,7 +600,7 @@
     <string name="status_bar_work" msgid="6022553324802866373">"Perfil de traballo"</string>
     <string name="status_bar_airplane" msgid="7057575501472249002">"Modo avión"</string>
     <string name="add_tile" msgid="2995389510240786221">"Engade un atallo"</string>
-    <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de emisión"</string>
+    <string name="broadcast_tile" msgid="3894036511763289383">"Atallo de difusión"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g> a menos que desactives esta opción antes desa hora"</string>
     <string name="zen_alarm_warning" msgid="444533119582244293">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
     <string name="alarm_template" msgid="3980063409350522735">"ás <xliff:g id="WHEN">%1$s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 1a15cf7..10aba24 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"રદ કરો"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"કન્ફર્મ કરો"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ફરી પ્રયાસ કરો"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ખાલી વિસ્તાર, પ્રમાણીકરણ રદ કરવા માટે ટૅપ કરો"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"પ્રમાણીકરણ રદ કરવા માટે ટૅપ કરો"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"કૃપા કરી ફરી પ્રયાસ કરો"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"તમારો ચહેરો શોધી રહ્યાં છીએ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ચહેરાનું પ્રમાણીકરણ થયું"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"પુષ્ટિ કરી"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"પરીક્ષણ પૂર્ણ કરવા કન્ફર્મ કરોને ટૅપ કરો"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"પ્રમાણિત"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ફિંગરપ્રિન્ટના સેન્સરને સ્પર્શ કરો"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ફિંગરપ્રિન્ટનું આઇકન"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"તમારા માટે શોધી રહ્યાં છે..."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 3c18b0d..9ce8e7d 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द करें"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"पुष्टि करें"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"फिर से कोशिश करें"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"कोई चेहरा नहीं मिला, टैप करके पुष्टि की प्रक्रिया रद्द करें"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"पुष्टि की प्रक्रिया रद्द करने के लिए टैप करें"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया फिर से कोशिश करें"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"आपके चेहरे की पुष्टि की जा रही है"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"चेहरे की पुष्टि हो गई"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"पुष्टि हो गई"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"\'पुष्टि करें\' पर टैप करके पूरा करें"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"पुष्टि हो गई"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फ़िंगरप्रिंट सेंसर को छुएं"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फ़िंगरप्रिंट आइकॉन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"आपको पहचान रहा है…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"कम अत्यावश्यक सूचनाएं नीचे दी गई हैं"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"खोलने के लिए फिर से टैप करें"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"खोलने के लिए ऊपर स्वाइप करें"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"फिर से कोशिश करने के लिए स्वाइप करें"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"फिर से कोशिश करने के लिए ऊपर की ओर स्वाइप करें"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"इस डिवाइस का प्रबंधन आपका संगठन करता है"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"इस डिवाइस के प्रबंधक <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> हैं"</string>
     <string name="phone_hint" msgid="4872890986869209950">"फ़ोन के लिए आइकॉन से स्वाइप करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 052a0d2..9c9b0b0 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Odustani"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Pokušaj ponovo"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazno područje, dodirnite da biste otkazali autentifikaciju"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Dodirnite da biste otkazali autentifikaciju"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pokušajte ponovo"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Traženje lica"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Lice je autentificirano"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrđeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Dodirnite Potvrdi za dovršetak"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentičnost provjerena"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dodirnite senzor otiska prsta"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona otiska prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Tražimo vas…"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 5f47e20..99a049e 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Mégse"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Megerősítés"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Újrapróbálkozás"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Üres régió. Koppintson a hitelesítés visszavonásához"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Koppintson a hitelesítés visszavonásához"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Próbálja újra"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Arc keresése"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Arc hitelesítve"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Megerősítve"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Koppintson a Megerősítés lehetőségre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Hitelesítve"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Érintse meg az ujjlenyomat-érzékelőt"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ujjlenyomat ikonja"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Keresem az Ön arcát…"</string>
@@ -647,7 +648,7 @@
     <string name="inline_minimize_button" msgid="966233327974702195">"Kis méret"</string>
     <string name="inline_silent_button_silent" msgid="5315879183296940969">"Néma"</string>
     <string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"Néma megjelenítés"</string>
-    <string name="inline_silent_button_alert" msgid="6008435419895088034">"Értesítések"</string>
+    <string name="inline_silent_button_alert" msgid="6008435419895088034">"Figyelemfelkeltő"</string>
     <string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"Értesítések folytatása"</string>
     <string name="inline_turn_off_notifications" msgid="8635596135532202355">"Az értesítések kikapcsolása"</string>
     <string name="inline_keep_showing_app" msgid="1723113469580031041">"Továbbra is megjelenjenek az alkalmazás értesítései?"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4d19cec..9ac0afb 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -55,12 +55,12 @@
     <string name="label_view" msgid="6304565553218192990">"Դիտել"</string>
     <string name="always_use_device" msgid="4015357883336738417">"Միշտ բացել <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածը, երբ <xliff:g id="USB_DEVICE">%2$s</xliff:g> լրասարքը միացված է"</string>
     <string name="always_use_accessory" msgid="3257892669444535154">"Միշտ բացել <xliff:g id="APPLICATION">%1$s</xliff:g> հավելվածը, երբ <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> լրասարքը միացված է"</string>
-    <string name="usb_debugging_title" msgid="4513918393387141949">"Թույլատրե՞լ USB-ի կարգաբերումը:"</string>
+    <string name="usb_debugging_title" msgid="4513918393387141949">"Թույլատրե՞լ USB-ով վրիպազերծումը"</string>
     <string name="usb_debugging_message" msgid="2220143855912376496">"Համակարգչի RSA-ի բանալի մատնահետքն է`\n<xliff:g id="FINGERPRINT">%1$s</xliff:g>"</string>
     <string name="usb_debugging_always" msgid="303335496705863070">"Միշտ թույլատրել այս համակարգչից"</string>
     <string name="usb_debugging_allow" msgid="2272145052073254852">"Թույլատրել"</string>
-    <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB վրիպազերծումը արգելված է"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ընթացիկ հաշվի օգտատերը չի կարող միացնել USB վրիպազերծումը: Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշվով:"</string>
+    <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB-ով վրիպազերծումը թույլատրված չէ"</string>
+    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ընթացիկ հաշվի միջոցով չեք կարող միացնել USB-ով վրիպազերծումը: Այս գործառույթը միացնելու համար մուտք գործեք հիմնական օգտատիրոջ հաշիվ:"</string>
     <string name="usb_contaminant_title" msgid="206854874263058490">"USB միացքն անջատված է"</string>
     <string name="usb_contaminant_message" msgid="7379089091591609111">"USB միացքն անջատվել է, որպեսզի ձեր սարքը չթրջվի կամ չաղտոտվի: Այժմ USB միացքի միջոցով հնարավոր չէ միացնել այլ սարքեր:\n\nԴուք ծանուցում կստանաք, երբ այն նորից անվտանգ լինի օգտագործել:"</string>
     <string name="usb_port_enabled" msgid="7906141351687694867">"USB միացքը միացվել է՝ լիցքավորիչներն ու լրասարքերը հայտնաբերելու համար"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Չեղարկել"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Հաստատել"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Նորից փորձել"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Ոչինչ չկա։ Հպեք՝ նույնականացումը չեղարկելու համար։"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Հպեք՝ նույնականացումը չեղարկելու համար"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Նորից փորձեք"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Դեմքի նույնականացում"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Դեմքը ճանաչվեց"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Հաստատվեց"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ավարտելու համար հպեք «Հաստատել»"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Նույնականացված է"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Հպեք մատնահետքի սկաներին"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Մատնահետքի պատկերակ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Դեմքի ճանաչում…"</string>
@@ -539,12 +540,12 @@
     <string name="accessibility_output_chooser" msgid="8185317493017988680">"Փոխել արտածման սարքը"</string>
     <string name="screen_pinning_title" msgid="3273740381976175811">"Էկրանն ամրացված է"</string>
     <string name="screen_pinning_description" msgid="8909878447196419623">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները:"</string>
-    <string name="screen_pinning_description_recents_invisible" msgid="8281145542163727971">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք Հետ և գլխավոր էկրանի կոճակները:"</string>
+    <string name="screen_pinning_description_recents_invisible" msgid="8281145542163727971">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք «Հետ» և «Գլխավոր էկրան» կոճակները"</string>
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար մատը սահեցրեք վեր և պահեք։"</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Էկրանը կմնա տեսադաշտում, մինչև այն ապամրացնեք: Ապամրացնելու համար հպեք և պահեք Համատեսք կոճակը:"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Էկրանը կցուցադրվի այնքան ժամանակ, մինչև որ չապամրացնեք այն: Ապամրացնելու համար հպեք և պահեք գլխավոր էկրանի կոճակը:"</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Էկրանն ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Էկրանն ապամրացնելու համար հպեք և պահեք Հետ և գլխավոր էկրանի կոճակները"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Էկրանն ապամրացնելու համար հպեք և պահեք «Հետ» և «Գլխավոր էկրան» կոճակները"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Էկրանն ապամրացնելու համար մատը սահեցրեք վերև և պահեք"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Եղավ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Ոչ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index b18beed..443e3b3 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Batal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Konfirmasi"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Coba lagi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Area kosong. Ketuk untuk membatalkan autentikasi"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ketuk untuk membatalkan autentikasi"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Coba lagi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Mencari wajah Anda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Wajah diautentikasi"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Dikonfirmasi"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ketuk Konfirmasi untuk menyelesaikan"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Diautentikasi"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sentuh sensor sidik jari"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon sidik jari"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Mencari wajah Anda…"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 129319d..f481e48 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Hætta við"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Staðfesta"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Reyna aftur"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Autt rými, ýttu til að hætta við greiningu"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ýttu til að hætta við auðkenningu"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Reyndu aftur"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Leitar að andliti þínu"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Andlit staðfest"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Staðfest"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ýttu á „Staðfesta“ til að ljúka"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Auðkennt"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Snertu fingrafaralesarann"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Fingrafaratákn"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Leitar að þér ..."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index a9427e5..718608a 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annulla"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Conferma"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Riprova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Spazio vuoto, tocca per annullare l\'autenticazione"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tocca per annullare l\'autenticazione"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Riprova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ricerca del tuo volto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Volto autenticato"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confermato"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tocca Conferma per completare"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticazione eseguita"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Tocca il sensore di impronte digitali"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icona dell\'impronta digitale"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"In attesa del volto…"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 52d4a13..606a097 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ביטול"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"אישור"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ניסיון נוסף"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"אזור ריק, יש להקיש עליו כדי לבטל את האימות"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"יש להקיש כדי לבטל את האימות"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"עליך לנסות שוב"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"המערכת מחפשת את הפנים שלך"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"זיהוי הפנים בוצע"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"מאושר"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"יש להקיש על \'אישור\' לסיום"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"מאומת"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"יש לגעת בחיישן טביעות האצבע"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"סמל טביעת אצבע"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"מחפש אותך…"</string>
@@ -463,7 +464,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"בזמן הקלטה או העברה, השירות שמספק את הפונקציה הזו יכול לקלוט מידע רגיש שמוצג במסך או מופעל מהמכשיר שלך, כולל מידע רגיש כמו אודיו, סיסמאות, פרטי תשלום, תמונות והודעות."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"חשיפת מידע רגיש בזמן העברה/הקלטה"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"אל תציג שוב"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"נקה הכל"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"ניקוי הכל"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"ניהול"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"התראות שקטות"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"ניקוי כל ההתראות השקטות"</string>
@@ -625,7 +626,7 @@
     <string name="clock_seconds_desc" msgid="6282693067130470675">"הצג שניות בשעון בשורת הסטטוס. פעולה זו עשויה להשפיע על אורך חיי הסוללה."</string>
     <string name="qs_rearrange" msgid="8060918697551068765">"סידור מחדש של הגדרות מהירות"</string>
     <string name="show_brightness" msgid="6613930842805942519">"הצג בהירות בהגדרות מהירות"</string>
-    <string name="experimental" msgid="6198182315536726162">"ניסיוניות"</string>
+    <string name="experimental" msgid="6198182315536726162">"ניסיוני"</string>
     <string name="enable_bluetooth_title" msgid="5027037706500635269">"‏האם להפעיל את ה-Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"‏כדי לחבר את המקלדת לטאבלט, תחילה עליך להפעיל את ה-Bluetooth."</string>
     <string name="enable_bluetooth_confirmation_ok" msgid="6258074250948309715">"הפעל"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 05f254f..0bc500c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"キャンセル"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"再試行"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白の領域をタップすると、認証をキャンセルできます"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"タップすると認証をキャンセルします"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"もう一度お試しください"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"顔を認証中です"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"顔を認証しました"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"確認しました"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"完了するには [確認] をタップしてください"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"認証済み"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"指紋認証センサーをタップしてください"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋アイコン"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"顔を認証しています…"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 6a7460d..b3c7de7 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"გაუქმება"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"დადასტურება"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ხელახლა ცდა"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"დაფიქსირდა ცარიელი უბანი, შეეხეთ ამოცნობის გასაუქმებლად"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"შეეხეთ ავტორიზაციის გასაუქმებლად"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"გთხოვთ, ცადოთ ხელახლა"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"მიმდინარეობს თქვენი სახის ძებნა"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"სახის ამოცნობილია"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"დადასტურებული"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"დასასრულებლად შეეხეთ „დადასტურებას“"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ავტორიზებულია"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"შეეხეთ თითის ანაბეჭდის სენსორს"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"თითის ანაბეჭდის ხატულა"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"მიმდინარეობს თქვენი ძიება…"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 6e0fca7..e1fa4b2b 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Бас тарту"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Растау"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Қайталап көріңіз"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Аймақта ештеңе көрсетілмеген. Оны басып, аутентификациядан бас тартыңыз."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Аутентификациядан бас тарту үшін түртіңіз."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Қайталап көріңіз."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Құрылғы бетіңізді талдап жатыр."</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Бет танылды."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Расталды"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Аяқтау үшін \"Растау\" түймесін түртіңіз."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аутентификацияланған"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Саусақ ізін оқу сканерін түртіңіз"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Саусақ ізі белгішесі"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Бет ізделуде…"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 16272fc..db93aa9 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"បោះ​បង់​"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"បញ្ជាក់"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ព្យាយាម​ម្ដង​ទៀត"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"កន្លែង​ទំនេរ សូមចុច​ដើម្បីបោះបង់​ការផ្ទៀងផ្ទាត់"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ចុចដើម្បីបោះបង់​ការផ្ទៀងផ្ទាត់"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"សូម​ព្យាយាម​ម្ដងទៀត"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"កំពុង​ផ្ទៀងផ្ទាត់​មុខរបស់អ្នក"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"បានផ្ទៀងផ្ទាត់​មុខ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"បានបញ្ជាក់"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ចុច \"បញ្ជាក់\" ដើម្បីបញ្ចប់"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"បាន​ផ្ទៀងផ្ទាត់"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ប៉ះ​ឧបករណ៍​ចាប់ស្នាម​ម្រាមដៃ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"រូបតំណាង​ស្នាម​ម្រាមដៃ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"កំពុងស្វែងរកអ្នក…"</string>
@@ -543,8 +544,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"វា​នឹង​នៅតែ​បង្ហាញ រហូតទាល់​តែអ្នក​ដកការដៅ។ អូសឡើងលើ​ឱ្យជាប់ ដើម្បី​ដក​ការដៅ។"</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"វា​នឹង​នៅតែ​បង្ហាញ រហូត​ទាល់​តែ​អ្នក​ដក​ការ​ដៅ។ សូម​សង្កត់​ប៊ូតុង​ទិដ្ឋភាពរួម​​ឲ្យ​ជាប់ ដើម្បី​ដក​ការ​ដៅ។"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"វា​នឹង​នៅតែ​បង្ហាញ រហូត​ទាល់​តែ​អ្នក​ដក​ការដៅ។ សូម​ចុច​ប៊ូតុង​ទំព័រដើម​ឱ្យ​ជាប់ ដើម្បី​ដក​ការ​ដៅ។"</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"ដើម្បី​ដក​ការ​ដៅ​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុង​ទិដ្ឋភាពរួម​ឱ្យ​ជាប់"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"ដើម្បី​ដក​ការ​ដៅ​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុងទំព័រដើម​ឱ្យ​ជាប់"</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"ដើម្បី​ដកខ្ទាស់​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុង​ទិដ្ឋភាពរួម​ឱ្យ​ជាប់"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"ដើម្បី​ដកខ្ទាស់​អេក្រង់​នេះ សូម​ចុច​ប៊ូតុង​ថយ​ក្រោយ និង​ប៊ូតុងទំព័រដើម​ឱ្យ​ជាប់"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"អូសឡើងលើ​ឱ្យជាប់ ដើម្បី​ដកការដៅ​អេក្រង់​នេះ"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"យល់​ហើយ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"ទេ អរគុណ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79c4f2d..98589ad 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ರದ್ದುಮಾಡಿ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ದೃಢೀಕರಿಸಿ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ಖಾಲಿ ಪ್ರದೇಶ, ದೃಢೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ದೃಢೀಕರಣವನ್ನು ರದ್ದುಗೊಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ನಿಮ್ಮ ಮುಖದ ದೃಢೀಕರಣಕ್ಕಾಗಿ ನಿರೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ಮುಖವನ್ನು ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ಪೂರ್ಣಗೊಳಿಸಲು ದೃಢೀಕರಿಸಿ ಅನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ದೃಢೀಕರಿಸಲಾಗಿದೆ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಸೆನ್ಸರ್‌‌ ಅನ್ನು ಸ್ಪರ್ಶಿಸಿ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಐಕಾನ್"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ನಿಮಗಾಗಿ ಹುಡುಕಲಾಗುತ್ತಿದೆ…"</string>
@@ -450,7 +451,7 @@
     <string name="user_remove_user_title" msgid="4681256956076895559">"ಬಳಕೆದಾರರನ್ನು ತೆಗೆದುಹಾಕುವುದೇ?"</string>
     <string name="user_remove_user_message" msgid="1453218013959498039">"ಈ ಬಳಕೆದಾರರ ಎಲ್ಲಾ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುವುದು."</string>
     <string name="user_remove_user_remove" msgid="7479275741742178297">"ತೆಗೆದುಹಾಕಿ"</string>
-    <string name="battery_saver_notification_title" msgid="8614079794522291840">"ಬ್ಯಾಟರಿ ರಕ್ಷಕ ಆನ್ ಆಗಿದೆ"</string>
+    <string name="battery_saver_notification_title" msgid="8614079794522291840">"ಬ್ಯಾಟರಿ ಸೇವರ್ ಆನ್ ಆಗಿದೆ"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"ಕಾರ್ಯಕ್ಷಮತೆ ಮತ್ತು ಹಿನ್ನೆಲೆ ಡೇಟಾವನ್ನು ಕಡಿಮೆ ಮಾಡುತ್ತದೆ"</string>
     <string name="battery_saver_notification_action_text" msgid="132118784269455533">"ಬ್ಯಾಟರಿ ಸೇವರ್‌ ಆಫ್ ಮಾಡಿ"</string>
     <string name="media_projection_dialog_text" msgid="8585357687598538511">"ರೆಕಾರ್ಡ್ ಮಾಡುವಾಗ ಅಥವಾ ಕ್ಯಾಸ್ಟಿಂಗ್ ಮಾಡುವಾಗ, ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯಂತಹ ಆಡಿಯೋ, ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ಮಾಹಿತಿ, ಫೋಟೋಗಳು ಮತ್ತು ಸಂದೇಶಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಡಿಸ್‌ಪ್ಲೇ ಮಾಡಿದ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಿಂದ ಪ್ಲೇ ಮಾಡಿದ ಯಾವುದೇ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯನ್ನು <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಕ್ಯಾಪ್ಚರ್ ಮಾಡಬಹುದು."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index b0a1671..03c277e 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -39,7 +39,7 @@
     <string name="battery_saver_start_action" msgid="8187820911065797519">"절전 모드 사용 설정"</string>
     <string name="status_bar_settings_settings_button" msgid="3023889916699270224">"설정"</string>
     <string name="status_bar_settings_wifi_button" msgid="1733928151698311923">"Wi-Fi"</string>
-    <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"자동 화면 회전"</string>
+    <string name="status_bar_settings_auto_rotation" msgid="3790482541357798421">"화면 자동 회전"</string>
     <string name="status_bar_settings_mute_label" msgid="554682549917429396">"무시"</string>
     <string name="status_bar_settings_auto_brightness_label" msgid="511453614962324674">"자동"</string>
     <string name="status_bar_settings_notifications" msgid="397146176280905137">"알림"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"취소"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"확인"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"다시 시도하세요."</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"비어 있는 공간, 탭하여 인증 취소"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"탭하여 인증 취소"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"다시 시도해 주세요."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"얼굴을 찾는 중"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"얼굴이 인증되었습니다."</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"확인함"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"완료하려면 확인을 탭하세요."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"인증됨"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"지문 센서를 터치하세요."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"지문 아이콘"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"찾는 중..."</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"아래에 덜 급한 알림 표시"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"다시 탭하여 열기"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"위로 스와이프하여 열기"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"위로 스와이프하여 다시 시도해 주세요."</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"위로 스와이프하여 다시 시도해 주세요"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"조직에서 관리하는 기기입니다."</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>에서 관리하는 기기입니다."</string>
     <string name="phone_hint" msgid="4872890986869209950">"전화 기능을 사용하려면 아이콘에서 스와이프하세요."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index dd985c6..c82e5b0 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Жокко чыгаруу"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Ырастоо"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Кайталоо"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Бош жер калып калды, аутентификацияны жокко чыгаруу үчүн таптап коюңуз"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Аныктыгын текшерүүнү жокко чыгаруу үчүн таптаңыз"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Кайра аракет кылыңыз"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Жүзүңүз изделүүдө"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Жүздүн аныктыгы текшерилди"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Ырасталды"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Бүтүрүү үчүн \"Ырастоо\" баскычын басыңыз"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аныктыгы текшерилди"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Манжа изинин сенсорун басыңыз"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Манжа изинин сүрөтчөсү"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Жүзүңүз изделүүдө…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Анчейин шашылыш эмес эскертмелер төмөндө"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Ачуу үчүн кайра таптап коюңуз"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Ачуу үчүн өйдө сүрүңүз"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Кайра аракет кылуу үчүн экранды өйдө сүрүңүз"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Кайталоо үчүн экранды өйдө сүрүңүз"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Бул түзмөк уюмуңуз тарабынан башкарылат"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Бул түзмөк <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> тарабынан башкарылат"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Сүрөтчөнү серпип телефонго өтүңүз"</string>
@@ -457,7 +458,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"Жаздырып же тышкы экранга чыгаруу учурунда, бул функцияны аткарып жаткан колдонмо ойноткон аудиоңуз, сырсөздөрүңүз, төлөө маалыматыңыз, сүрөттөрүңүз жана билдирүүлөрүңүз сыяктуу экранда көрсөтүлгөн купуя маалыматты жаздырып калышы мүмкүн."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Тышкы экранга чыгарууда/жаздырууда купуя маалыматты ачыктоо"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Экинчи көрсөтүлбөсүн"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Бардыгын тазалап салуу"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Баарын тазалап салуу"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Башкаруу"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"Үнсүз билдирмелер"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Маанилүү эмес билдирмелердин баарын өчүрүү"</string>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 90e78e8..5a8c5dc 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -36,4 +36,8 @@
 
     <dimen name="volume_tool_tip_right_margin">136dp</dimen>
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
+
+    <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
+         value in landscape since we have limited vertical space-->
+    <dimen name="bubble_padding_top">4dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index d99db74..4f4d291 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ຍົກເລີກ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ຢືນຢັນ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ລອງໃໝ່"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ພື້ນທີ່ຫວ່າງເປົ່າ, ແຕະເພື່ອຍົກເລີກການພິສູດຢືນຢັນ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ແຕະເພື່ອຍົກເລີກການກວດສອບຄວາມຖືກຕ້ອງ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ກະລຸນາລອງໃໝ່"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ກຳລັງເບິ່ງໃບໜ້າຂອງທ່ານ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ພິສູດຢືນຢັນໃບໜ້າແລ້ວ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ຢືນຢັນແລ້ວ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ແຕະຢືນຢັນເພື່ອສຳເລັດ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ຮັບຮອງຄວາມຖືກຕ້ອງແລ້ວ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ແຕະໃສ່ເຊັນເຊີລາຍນິ້ວມື"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ໄອຄອນລາຍນິ້ວມື"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ກຳລັງຊອກຫາທ່ານ…"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 0355fc0..ca7c89e 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Atšaukti"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Patvirtinkite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Bandyti dar kartą"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tuščia sritis; palieskite, kad atšauktumėte autentifikavimą"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Palieskite, jei norite atšaukti autentifikavimą"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Bandykite dar kartą"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ieškoma veido"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Veidas autentifikuotas"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Patvirtinta"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Paliesk. „Patvirtinti“, kad užbaigtumėte"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikuota"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Palieskite piršto antspaudo jutiklį"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Piršto antspaudo piktograma"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ieškoma jūsų…"</string>
@@ -511,7 +512,7 @@
     <string name="monitoring_description_do_header_with_name" msgid="5511133708978206460">"„<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>“ naudoja „<xliff:g id="DEVICE_OWNER_APP">%2$s</xliff:g>“ įrenginiui tvarkyti."</string>
     <string name="monitoring_description_do_body" msgid="3639594537660975895">"Administrat. gali stebėti ir tvark. nustat., įmonės prieigos par., progr., su įreng. susietus duomenis ir įreng. vietovės inform."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
-    <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Sužinoti daugiau"</string>
+    <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"Sužinokite daugiau"</string>
     <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"Esate prisijungę prie programos „<xliff:g id="VPN_APP">%1$s</xliff:g>“, kuri gali stebėti tinklo veiklą, įskaitant el. laiškus, programas ir svetaines."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"Atidaryti VPN nustatymus"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e6ac235..424e104 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Atcelt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Apstiprināt"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Mēģināt vēlreiz"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tukšs apgabals. Pieskarieties tam, lai atceltu autentificēšanu."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Pieskarieties, lai atceltu autentifikāciju."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Lūdzu, mēģiniet vēlreiz."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Tiek meklēta jūsu seja"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Seja autentificēta"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Apstiprināts"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Lai pabeigtu, pieskarieties Apstiprināt"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentifikācija veikta"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Pieskarieties pirksta nospieduma sensoram"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Pirksta nospieduma ikona"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Notiek jūsu sejas meklēšana…"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index a529dfe..dd86ede 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Откажи"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потврди"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Обиди се повторно"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празен регион, допрете за да ја откажете проверката"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Допрете за да ја откажете проверката"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Обидете се повторно"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Го бараме вашето лице"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицето е проверено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потврдено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Допрете „Потврди“ за да се заврши"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Проверена"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Допрете го сензорот за отпечатоци"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона за отпечатоци"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ве бараме вас…"</string>
@@ -192,7 +193,7 @@
     <string name="not_default_data_content_description" msgid="9194667237765917844">"Не е поставен да користи интернет"</string>
     <string name="cell_data_off" msgid="1051264981229902873">"Исклучи"</string>
     <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Се поврзува со Bluetooth."</string>
-    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Режим на работа во авион."</string>
+    <string name="accessibility_airplane_mode" msgid="834748999790763092">"Авионски режим."</string>
     <string name="accessibility_vpn_on" msgid="5993385083262856059">"VPN е вклучена."</string>
     <string name="accessibility_no_sims" msgid="3957997018324995781">"Нема SIM-картичка"</string>
     <string name="carrier_network_change_mode" msgid="8149202439957837762">"Променување на мрежата на операторот"</string>
@@ -347,7 +348,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="8599674057673605368">"Осветленост"</string>
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"Автоматски"</string>
     <string name="quick_settings_inversion_label" msgid="8790919884718619648">"Преврти ги боите"</string>
-    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Режим за корекција на боја"</string>
+    <string name="quick_settings_color_space_label" msgid="853443689745584770">"Режим за корекција на бои"</string>
     <string name="quick_settings_more_settings" msgid="326112621462813682">"Повеќе поставки"</string>
     <string name="quick_settings_done" msgid="3402999958839153376">"Готово"</string>
     <string name="quick_settings_connected" msgid="1722253542984847487">"Поврзано"</string>
@@ -457,7 +458,7 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"При снимањето или емитувањето, услугата што ја обезбедува функцијава може да ги сними чувствителните информации што се прикажани на вашиот екран или пуштени од вашиот уред, вклучувајќи чувствителни информации како што се аудио, лозинки, информации за плаќање, фотографии и пораки."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Изложување чувствителни информации при емитување/снимање"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Не покажувај повторно"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Исчисти сè"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Управувајте"</string>
     <string name="notification_section_header_gentle" msgid="4372438504154095677">"Тивки известувања"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Исчисти ги сите тивки известувања"</string>
@@ -597,7 +598,7 @@
     <string name="status_bar_ethernet" msgid="5044290963549500128">"Етернет"</string>
     <string name="status_bar_alarm" msgid="8536256753575881818">"Аларм"</string>
     <string name="status_bar_work" msgid="6022553324802866373">"Работен профил"</string>
-    <string name="status_bar_airplane" msgid="7057575501472249002">"Режим на работа во авион"</string>
+    <string name="status_bar_airplane" msgid="7057575501472249002">"Авионски режим"</string>
     <string name="add_tile" msgid="2995389510240786221">"Додај плочка"</string>
     <string name="broadcast_tile" msgid="3894036511763289383">"Емитувај плочка"</string>
     <string name="zen_alarm_warning_indef" msgid="3482966345578319605">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g> освен ако претходно не го исклучите ова"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 181eb4a..859e65b 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"റദ്ദാക്കുക"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"സ്ഥിരീകരിക്കുക"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"വീണ്ടും ശ്രമിക്കുക"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ശൂന്യമായ ഇടം, പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"പരിശോധിച്ചുറപ്പിക്കൽ റദ്ദാക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"വീണ്ടും ശ്രമിക്കുക"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"നിങ്ങളുടെ മുഖത്തിന് വേണ്ടി തിരയുന്നു"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"മുഖം പരിശോധിച്ചുറപ്പിച്ചു"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"സ്ഥിരീകരിച്ചു"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"പൂർത്തിയാക്കാൻ സ്ഥിരീകരിക്കുക ടാപ്പ് ചെയ്യൂ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"പരിശോധിച്ചുറപ്പിച്ചു"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"വിരലടയാള സെൻസർ സ്‌പർശിക്കുക"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"വിരലടയാള ഐക്കൺ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"നിങ്ങൾക്കായി തിരയുന്നു…"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 085a0aeeb..3363af7 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Цуцлах"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Баталгаажуулах"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Дахин оролдох"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Нотолгоог цуцлахын тулд хоосон бүсийг товшино уу"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Нотолгоог цуцлахын тулд товшино уу"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Дахин оролдоно уу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Таны царайг хайж байна"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Царайг баталгаажууллаа"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Баталгаажсан"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Дуусгахын тулд баталгаажуулахыг товших"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Баталгаажуулагдсан"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Хурууны хээ мэдрэгчид хүрэх"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Хурууны хээний дүрс тэмдэг"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Таныг хайж байна…"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 4722ae9..f785fe9 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द करा"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"खात्री करा"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"पुन्हा प्रयत्न करा"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"प्रदेशाचे नाव रिक्त आहे, ऑथेंटिकेशन रद्द करण्यासाठी टॅप करा"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ऑथेंटिकेशन रद्द करण्यासाठी टॅप करा"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया पुन्हा प्रयत्न करा"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"तुमचा चेहरा शोधत आहे"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"चेहरा ऑथेंटिकेशन केलेला आहे"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"निश्चित केले"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"पूर्ण करण्यासाठी खात्री करा वर टॅप करा"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ऑथेंटिकेशन केलेले"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फिंगरप्रिंट सेन्सरला स्पर्श करा"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फिंगरप्रिंट आयकन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"तुमच्यासाठी शोधत आहे…"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 1bc7ca9..352c376 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Batal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Sahkan"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Cuba lagi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Kawasan kosong. Ketik untuk membatalkan pengesahan"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Ketik untuk membatalkan pengesahan"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Sila cuba lagi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Mencari wajah anda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Wajah disahkan"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Disahkan"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Ketik Sahkan untuk menyelesaikan"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Disahkan"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Sentuh penderia cap jari"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon cap jari"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Mencari anda…"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 51aaaa2..d136042 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"မလုပ်တော့"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"အတည်ပြုပါ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ထပ်စမ်းကြည့်ရန်"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"နေရာလွတ်၊ အထောက်အထားစိစစ်ခြင်းကို မလုပ်တော့ရန် တို့ပါ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်ရန် တို့ပါ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ထပ်စမ်းကြည့်ပါ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"သင့်မျက်နှာကို ရှာနေသည်"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"မျက်နှာ အထောက်အထားစိစစ်ပြီးပြီ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"အတည်ပြုပြီးပြီ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"အပြီးသတ်ရန်အတွက် \'အတည်ပြုရန်\' ကို တို့ပါ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"အထောက်အထားစိစစ်ပြီးပြီ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"လက်ဗွေအာရုံခံကိရိယာကို တို့ပါ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"လက်ဗွေ သင်္ကေတ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"သင့်ကို ရှာဖွေနေသည်…"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index ce611e0..86f0b38 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Avbryt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekreft"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Prøv på nytt"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Tomt område – trykk for å avbryte autentisering"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Trykk for å avbryte autentiseringen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Prøv igjen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ser etter ansiktet ditt"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansiktet er autentisert"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekreftet"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Trykk på Bekreft for å fullføre"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentisert"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Trykk på fingeravtrykkssensoren"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon for fingeravtrykk"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Ser etter deg …"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 6ef85b5..4c17904 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"रद्द गर्नुहोस्"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"पुष्टि गर्नुहोस्"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"फेरि प्रयास गर्नुहोस्"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"खाली क्षेत्र, प्रमाणीकरण रद्द गर्न ट्याप गर्नुहोस्"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"प्रमाणीकरण रद्द गर्न ट्याप गर्नुहोस्"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"कृपया फेरि प्रयास गर्नुहोस्"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"तपाईंको अनुहार खोज्दै"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"अनुहार प्रमाणीकरण गरियो"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"पुष्टि भयो"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"पूरा गर्नका लागि पुष्टि गर्नुहोस् नामक विकल्पमा ट्याप गर्नुहोस्"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"प्रमाणीकरण गरियो"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्‌"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"फिंगरप्रिन्ट जनाउने आइकन"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"तपाईंलाई खोज्दै…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न परिदृश्य बटनलाई छोइराख्नुहोस्।"</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"तपाईंले अनपिन नगरेसम्म यसले त्यसलाई दृश्यमा कायम राख्छ। अनपिन गर्न गृह नामक बटनलाई छोइराख्नुहोस्।"</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"यस स्क्रिनलाई अनपनि गर्न पछाडि र परिदृश्य नामक बटनहरूलाई छोइराख्नुहोस्"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"यस स्क्रिनलाई अनपनि गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"यस स्क्रिनलाई अनपिन गर्न पछाडि र गृह नामक बटनहरूलाई छोइराख्नुहोस्"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"यो स्क्रिन अनपिन गर्न माथितिर स्वाइप गरी थिचिराख्नुहोस्"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"बुझेँ"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"धन्यवाद पर्दैन"</string>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 7b55d18..0febc8e 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -83,4 +83,9 @@
     <color name="biometric_dialog_accent">#ff80cbc4</color> <!-- light teal -->
     <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
 
+    <color name="GM2_green_500">#FF41Af6A</color>
+    <color name="GM2_blue_500">#5195EA</color>
+    <color name="GM2_red_500">#E25142</color>
+    <color name="GM2_yellow_500">#F5C518</color>
+
 </resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 305b46a..c62c579 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Annuleren"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bevestigen"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Opnieuw proberen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Leeg gebied. Tik om de verificatie te annuleren."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tik om de verificatie te annuleren"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Probeer het opnieuw"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Er wordt naar je gezicht gezocht"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Gezicht geverifieerd"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bevestigd"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tik op Bevestigen om te voltooien"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Geverifieerd"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Raak de vingerafdruksensor aan"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Vingerafdrukpictogram"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Jouw gezicht zoeken…"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index e9b150c..d295925 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ନିଶ୍ଚିତ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ଖାଲି ଅଞ୍ଚଳ, ପ୍ରାମାଣିକତା ବାତିଲ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ପ୍ରାମାଣିକତା ବାତିଲ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ଆପଣଙ୍କର ମୁହଁକୁ ପ୍ରମାଣ କରୁଛି"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ମୁହଁ ପ୍ରାମାଣିକତା ହୋଇଛି"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ସୁନିଶ୍ଚିତ କରାଯାଇଛି"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ସୁନିଶ୍ଚିତ କରନ୍ତୁରେ ଟାପ୍ କରନ୍ତୁ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ପ୍ରାମାଣିକତା ହୋଇଛି"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ଟିପଚିହ୍ନ ସେନସର୍‌କୁ ଛୁଅଁନ୍ତୁ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ଆଙ୍ଗୁଠି ଚିହ୍ନ ଆଇକନ୍"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ଆପଣଙ୍କୁ ଚିହ୍ନଟ କରୁଛି…"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 742080c..b75deb9 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ਰੱਦ ਕਰੋ"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ਪੁਸ਼ਟੀ ਕਰੋ"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ਖਾਲੀ ਖੇਤਰ, ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ਪ੍ਰਮਾਣੀਕਰਨ ਰੱਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ਤੁਹਾਡਾ ਚਿਹਰਾ ਲੱਭਿਆ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ਚਿਹਰਾ ਪ੍ਰਮਾਣੀਕਿਰਤ ਹੋਇਆ"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ਪੁਸ਼ਟੀ ਕੀਤੀ ਗਈ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"ਪੂਰਾ ਕਰਨ ਲਈ ਪੁਸ਼ਟੀ ਕਰੋ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ਪ੍ਰਮਾਣਿਤ ਹੋਇਆ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸੈਂਸਰ ਨੂੰ ਸਪੱਰਸ਼ ਕਰੋ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਦਾ ਪ੍ਰਤੀਕ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ਤੁਹਾਡੀ ਪਛਾਣ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"ਹੇਠਾਂ ਘੱਟ ਲਾਜ਼ਮੀ ਸੂਚਨਾਵਾਂ"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"ਖੋਲ੍ਹਣ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"ਖੋਲ੍ਹਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਉੱਤੇ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਤੁਹਾਡੇ ਸੰਗਠਨ ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"ਇਹ ਡੀਵਾਈਸ <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> ਵੱਲੋਂ ਪ੍ਰਬੰਧਿਤ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
     <string name="phone_hint" msgid="4872890986869209950">"ਫ਼ੋਨ ਲਈ ਪ੍ਰਤੀਕ ਤੋਂ ਸਵਾਈਪ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 93a2c12..d372b3d 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anuluj"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potwierdź"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Spróbuj jeszcze raz"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Pusty obszar, kliknij, by anulować uwierzytelnianie"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Kliknij, by anulować uwierzytelnianie"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Spróbuj ponownie"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Szukam Twojej twarzy"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Twarz rozpoznana"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potwierdzono"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Aby zakończyć, kliknij Potwierdź"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Uwierzytelniono"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotknij czytnika linii papilarnych"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona odcisku palca"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Szukam Cię…"</string>
@@ -465,10 +466,10 @@
     <string name="media_projection_dialog_service_text" msgid="3075544489835858258">"Podczas nagrywania lub przesyłania usługa udostępniająca tę funkcję może rejestrować wszelkie informacje poufne wyświetlane na ekranie lub odtwarzane na urządzeniu takie jak dźwięki czy podawane hasła, informacje o płatnościach, zdjęcia i wiadomości."</string>
     <string name="media_projection_dialog_title" msgid="8124184308671641248">"Ujawnianie poufnych informacji podczas przesyłania/nagrywania"</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Nie pokazuj ponownie"</string>
-    <string name="clear_all_notifications_text" msgid="814192889771462828">"Ukryj wszystkie"</string>
+    <string name="clear_all_notifications_text" msgid="814192889771462828">"Usuń wszystkie"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Zarządzaj"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Powiadomienia ciche"</string>
-    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Wyczyść wszystkie ciche powiadomienia"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Ciche powiadomienia"</string>
+    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Usuń wszystkie ciche powiadomienia"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"Powiadomienia wstrzymane przez tryb Nie przeszkadzać"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"Rozpocznij teraz"</string>
     <string name="empty_shade_text" msgid="708135716272867002">"Brak powiadomień"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 7a57049..2b76157 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque nela para cancelar a autenticação"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Procurando seu rosto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em \"Confirmar\" para concluir"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressão digital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Procurando você…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Para liberar esta tela, mantenha os botões Voltar e Visão geral pressionados"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar essa tela, toque nos botões Voltar e Início e mantenha-os pressionados"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar esta tela, mantenha os botões Voltar e Início pressionados"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para liberar esta tela, deslize para cima e pressione"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index fb45ea7..17cc86f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque para cancelar a autenticação."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação."</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente."</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"A procurar o seu rosto…"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmado"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em Confirmar para concluir."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressões digitais."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"À sua procura…"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 7a57049..2b76157 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Cancelar"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmar"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tentar novamente"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Região vazia. Toque nela para cancelar a autenticação"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Toque para cancelar a autenticação"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tente novamente"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Procurando seu rosto"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Rosto autenticado"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmada"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Toque em \"Confirmar\" para concluir"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autenticado"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Toque no sensor de impressão digital"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ícone de impressão digital"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Procurando você…"</string>
@@ -544,7 +545,7 @@
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Ela é mantida à vista até que seja liberada. Toque em Visão geral e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Ela é mantida à vista até que seja liberada. Toque em Início e mantenha essa opção pressionada para liberar."</string>
     <string name="screen_pinning_toast" msgid="2266705122951934150">"Para liberar esta tela, mantenha os botões Voltar e Visão geral pressionados"</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar essa tela, toque nos botões Voltar e Início e mantenha-os pressionados"</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Para liberar esta tela, mantenha os botões Voltar e Início pressionados"</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Para liberar esta tela, deslize para cima e pressione"</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"Entendi"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Não, obrigado"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index e897f16..db8f87d 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anulați"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Confirmați"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Încercați din nou"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Zonă goală, atingeți pentru a anula autentificarea"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Atingeți pentru a anula autentificarea"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Încercați din nou"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Se caută chipul"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Chip autentificat"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Confirmat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Atingeți Confirmați pentru a finaliza"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentificat"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Atingeți senzorul de amprente"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Pictograma amprentă"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Vă căutăm…"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index c68f81e..f7527d9 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Отмена"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Подтвердить"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Повторить попытку"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Пустая область. Нажмите на нее, чтобы отменить аутентификацию."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Нажмите, чтобы отменить аутентификацию"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Повторите попытку"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Распознавание лица"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лицо распознано"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Подтверждено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Нажмите \"Подтвердить\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Аутентификация выполнена"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Прикоснитесь к сканеру отпечатков пальцев."</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок отпечатка пальца"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Поиск лица…"</string>
@@ -405,7 +406,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Показать менее важные уведомления"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Нажмите ещё раз, чтобы открыть"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Проведите вверх, чтобы открыть"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Чтобы повторить попытку, проведите по экрану вверх."</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Чтобы повторить попытку, проведите вверх"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Этим устройством управляет ваша организация"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Этим устройством управляет компания \"<xliff:g id="ORGANIZATION_NAME">%s</xliff:g>\""</string>
     <string name="phone_hint" msgid="4872890986869209950">"Телефон: проведите от значка"</string>
@@ -549,8 +550,8 @@
     <string name="screen_pinning_description_gestural" msgid="1191513974909607884">"Экран будет зафиксирован, пока вы не отмените блокировку (для этого нужно провести вверх и удерживать)."</string>
     <string name="screen_pinning_description_accessible" msgid="426190689254018656">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Обзор\"."</string>
     <string name="screen_pinning_description_recents_invisible_accessible" msgid="6134833683151189507">"Приложение останется активным, пока вы не отмените блокировку, нажав и удерживая кнопку \"Главный экран\"."</string>
-    <string name="screen_pinning_toast" msgid="2266705122951934150">"Чтобы отменить блокировку, нажмите и удерживайте кнопки \"Назад\" и \"Обзор\""</string>
-    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Чтобы отменить блокировку, нажмите и удерживайте кнопки \"Назад\" и \"Главный экран\""</string>
+    <string name="screen_pinning_toast" msgid="2266705122951934150">"Чтобы открепить экран, нажмите и удерживайте кнопки \"Назад\" и \"Обзор\"."</string>
+    <string name="screen_pinning_toast_recents_invisible" msgid="8252402309499161281">"Чтобы открепить экран, нажмите и удерживайте кнопки \"Назад\" и \"Главный экран\"."</string>
     <string name="screen_pinning_toast_gesture_nav" msgid="5070548776081664958">"Чтобы открепить этот экран, проведите по нему вверх и задержите руку в крайнем положении."</string>
     <string name="screen_pinning_positive" msgid="3783985798366751226">"ОК"</string>
     <string name="screen_pinning_negative" msgid="3741602308343880268">"Нет, спасибо"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 2c54218..c87ef5a 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"අවලංගු කරන්න"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"තහවුරු කරන්න"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"නැවත උත්සාහ කරන්න"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"හිස් කලාපය, සත්‍යාපනය අවලංගු කිරීමට තට්ටු කරන්න"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"සත්‍යාපනය අවලංගු කිරීමට තට්ටු කරන්න"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"නැවත උත්සාහ කරන්න"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"ඔබේ මුහුණ සොයනු ලැබේ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"මුහුණ සත්‍යාපන කළා"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"තහවුරු කළා"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"සම්පූර්ණ කිරීමට තහවුරු කරන්න තට්ටු කර."</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"සත්‍යාපනය විය"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"ඇඟිලි සලකුණු සංවේදකය ස්පර්ශ කරන්න"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ඇඟිලි සලකුණු නිරූපකය"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"ඔබව සොයමින්…"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index d14f928..1e171a1 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Zrušiť"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potvrdiť"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Skúsiť znova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prázdna oblasť, klepnutím zrušte overenie"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Klepnutím zrušíte overenie"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Skúste to znova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Hľadá sa vaša tvár"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Tvár bola overená"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potvrdené"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Overenie dokončíte klepnutím na Potvrdiť"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Overené"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotknite sa senzora odtlačkov prstov"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona odtlačku prsta"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hľadáme vás…"</string>
@@ -405,7 +406,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Menej naliehavé upozornenia sa nachádzajú nižšie"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Upozornenie otvoríte opätovným klepnutím"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Otvorte potiahnutím prstom nahor"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Potiahnutím nahor to skúsite znova"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Potiahnutím nahor to skúste znova"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Toto zariadenie spravuje vaša organizácia"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Toto zariadenie spravuje organizácia <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Telefón otvoríte prejdením prstom od ikony"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 63358ba..b342ce6 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Prekliči"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Potrdite"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Poskusi znova"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Prazno območje. Dotaknite se, da prekličete preverjanje pristnosti."</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Če želite preklicati preverjanje pristnosti, se dotaknite"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Poskusite znova"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Iskanje obraza"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Pristnost obraza je potrjena"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Potrjeno"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Za dokončanje se dotaknite »Potrdite«"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Preverjena pristnost"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Dotaknite se tipala prstnih odtisov"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona prstnih odtisov"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Preverjanje vašega obraza …"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index bd026dc..4fba770 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -96,7 +96,7 @@
     <string name="usb_preference_title" msgid="6551050377388882787">"Opsionet e transferimit të dosjeve të USB-së"</string>
     <string name="use_mtp_button_title" msgid="4333504413563023626">"Lidh si një lexues \"media\" (MTP)"</string>
     <string name="use_ptp_button_title" msgid="7517127540301625751">"Montoje si kamerë (PTP)"</string>
-    <string name="installer_cd_button_title" msgid="2312667578562201583">"Instalo apl. \"Transferimi i skedarëve\" për \"Mac\""</string>
+    <string name="installer_cd_button_title" msgid="2312667578562201583">"Instalo \"Transferimi i skedarëve të Android\" për Mac"</string>
     <string name="accessibility_back" msgid="567011538994429120">"Prapa"</string>
     <string name="accessibility_home" msgid="8217216074895377641">"Faqja bazë"</string>
     <string name="accessibility_menu" msgid="316839303324695949">"Menyja"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Anulo"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Konfirmo"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Provo përsëri"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Rajon bosh, trokit për të anuluar vërtetimin"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Trokit për të anuluar vërtetimin"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Provo përsëri"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Po kërkon për fytyrën tënde"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Fytyra u vërtetua"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Konfirmuar"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Trokit \"Konfirmo\" për ta përfunduar"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"U vërtetua"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Prek sensorin e gjurmës së gishtit"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikona e gjurmës së gishtit"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Po të kërkojmë…"</string>
@@ -298,7 +299,7 @@
     <string name="dessert_case" msgid="1295161776223959221">"\"Kutia e ëmbëlsirës\""</string>
     <string name="start_dreams" msgid="5640361424498338327">"Mbrojtësi i ekranit"</string>
     <string name="ethernet_label" msgid="7967563676324087464">"Eternet"</string>
-    <string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"Trokit dhe mbaj prekur ikonat për më shumë opsione"</string>
+    <string name="quick_settings_header_onboarding_text" msgid="8030309023792936283">"Trokit dhe mbaj të shtypur ikonat për më shumë opsione"</string>
     <string name="quick_settings_dnd_label" msgid="7112342227663678739">"Mos shqetëso"</string>
     <string name="quick_settings_dnd_priority_label" msgid="483232950670692036">"Vetëm me prioritet"</string>
     <string name="quick_settings_dnd_alarms_label" msgid="2559229444312445858">"Vetëm alarmet"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 4d289dc..310fc6f 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Откажи"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Потврди"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Пробај поново"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Празна област, додирните да бисте отказали потврду идентитета"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Додирните да бисте отказали потврду идентитета"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Пробајте поново"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Тражи се ваше лице"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Лице је потврђено"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Потврђено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Додирните Потврди да бисте завршили"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Идентитет је потврђен"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Додирните сензор за отисак прста"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Икона отиска прста"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Тражимо вас…"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index b891b93..54769cd 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Avbryt"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Bekräfta"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Försök igen"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Området är tomt. Tryck för att avbryta autentiseringen"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tryck för att avbryta autentiseringen"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Försök igen"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Söker efter ditt ansikte"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ansiktet har autentiserats"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Bekräftat"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Slutför genom att trycka på Bekräfta"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Autentiserad"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Tryck på fingeravtryckssensorn"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Ikon för fingeravtryck"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Håller utkik efter dig …"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b37405a..3f9278a 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Ghairi"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Thibitisha"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Jaribu tena"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Eneo lisilo na chochote, gusa ili ughairi uthibitishaji"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Gusa ili ughairi uthibitishaji"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Tafadhali jaribu tena"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Inatafuta uso wako"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Uso umethibitishwa"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Imethibitishwa"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Gusa Thibitisha ili ukamilishe"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Umethibitishwa"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Gusa kitambua alama ya kidole"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Aikoni ya alama ya kidole"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Inakutafuta…"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index d56ccef..0f36889 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ரத்துசெய்"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"உறுதிப்படுத்துக"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"மீண்டும் முயல்க"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"காலியான பகுதி, அங்கீகாரத்தை ரத்துசெய்யத் தட்டவும்"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"பயோமெட்ரிக் அடையாளத்தை ரத்துசெய்ய தட்டவும்"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"மீண்டும் முயலவும்"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"உங்கள் முகத்தை அங்கீகரிக்கிறது"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"முகம் அங்கீகரிக்கப்பட்டது"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"உறுதிப்படுத்தப்பட்டது"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"முடிக்க \'உறுதிப்படுத்து\' என்பதை தட்டவும்"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"அங்கீகரிக்கப்பட்டது"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"கைரேகை சென்சாரைத் தொடவும்"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"கைரேகை ஐகான்"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"உங்கள் முகத்தைத் தேடுகிறது…"</string>
@@ -506,18 +507,18 @@
     <string name="monitoring_description_do_body" msgid="3639594537660975895">"உங்கள் நிர்வாகியால் அமைப்புகள், நிறுவன அணுகல், ஆப்ஸ், சாதனத்துடன் தொடர்புடைய டேட்டா, சாதன இருப்பிடத் தகவல் ஆகியவற்றைக் கண்காணிக்கவும் நிர்வகிக்கவும் முடியும்."</string>
     <string name="monitoring_description_do_learn_more_separator" msgid="3785251953067436862">" "</string>
     <string name="monitoring_description_do_learn_more" msgid="1849514470437907421">"மேலும் அறிக"</string>
-    <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_do_body_vpn" msgid="8255218762488901796">"<xliff:g id="VPN_APP">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_vpn_settings_separator" msgid="1933186756733474388">" "</string>
     <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"VPN அமைப்புகளைத் திற"</string>
     <string name="monitoring_description_ca_cert_settings_separator" msgid="4987350385906393626">" "</string>
     <string name="monitoring_description_ca_cert_settings" msgid="5489969458872997092">"நம்பகமான அனுமதிச் சான்றுகளைத் திற"</string>
     <string name="monitoring_description_network_logging" msgid="7223505523384076027">"உங்கள் நிர்வாகி நெட்வொர்க் பதிவெடுத்தலை இயக்கியுள்ளார், இது சாதனத்தில் ட்ராஃபிக்கைக் கண்காணிக்கும்.\n\nமேலும் தகவலுக்கு, உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
-    <string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN இணைப்பை அமைக்க, பயன்பாட்டிற்கு அனுமதி வழங்கியுள்ளீர்கள்.\n\nஇந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட, உங்கள் சாதனத்தையும் நெட்வொர்க் செயல்பாட்டையும் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_vpn" msgid="4445150119515393526">"VPN இணைப்பை அமைக்க, பயன்பாட்டிற்கு அனுமதி வழங்கியுள்ளீர்கள்.\n\nஇந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட, உங்கள் சாதனத்தையும் நெட்வொர்க் செயல்பாட்டையும் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது.\n\nஉங்கள் நிர்வாகியால் பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்.\n\nமேலும் தகவலுக்கு, உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்.\n\nஉங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய VPN உடனும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
     <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
     <string name="monitoring_description_app" msgid="1828472472674709532">"மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள்."</string>
-    <string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
-    <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்தப் பயன்பாட்டால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="monitoring_description_app_personal" msgid="484599052118316268">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால், மின்னஞ்சல்கள், பயன்பாடுகள் மற்றும் இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
+    <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"<xliff:g id="APPLICATION">%1$s</xliff:g> உடன் இணைக்கப்பட்டுள்ளீர்கள். இந்த ஆப்ஸால் மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்க முடியும்."</string>
     <string name="monitoring_description_app_work" msgid="4612997849787922906">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nமேலும் தகவலுக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
     <string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"உங்கள் பணிக் கணக்கை <xliff:g id="ORGANIZATION">%1$s</xliff:g> நிர்வகிக்கிறது. மின்னஞ்சல்கள், பயன்பாடுகள், இணையதளங்கள் உட்பட உங்கள் பணி நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> உடன் அது இணைக்கப்பட்டுள்ளது.\n\nஉங்கள் தனிப்பட்ட நெட்வொர்க் செயல்பாட்டைக் கண்காணிக்கக்கூடிய <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> உடனும் இணைக்கப்பட்டுள்ளீர்கள்."</string>
     <string name="keyguard_indication_trust_unlocked" msgid="2712865815371519117">"TrustAgent இதைத் திறந்தே வைத்துள்ளது"</string>
@@ -845,7 +846,7 @@
     <string name="pip_skip_to_prev" msgid="1955311326688637914">"முந்தையதற்குச் செல்"</string>
     <string name="thermal_shutdown_title" msgid="4458304833443861111">"வெப்பத்தினால் ஃபோன் ஆஃப் செய்யப்பட்டது"</string>
     <string name="thermal_shutdown_message" msgid="9006456746902370523">"இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது"</string>
-    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n	• அதிகளவு தரவைப் பயன்படுத்தும் பயன்பாடுகளை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n	• பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n	• அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
+    <string name="thermal_shutdown_dialog_message" msgid="566347880005304139">"உங்கள் ஃபோன் அதிகமாகச் சூடானதால், அதன் சூட்டைக் குறைக்க, ஆஃப் செய்யப்பட்டது. இப்போது உங்கள் ஃபோன் இயல்புநிலையில் இயங்குகிறது.\n\nபின்வருவனவற்றைச் செய்தால், ஃபோன் சூடாகலாம்:\n	• அதிகளவு தரவைப் பயன்படுத்தும் ஆப்ஸை (எ.கா: கேமிங், வீடியோ (அ) வழிகாட்டுதல் பயன்பாடுகள்) பயன்படுத்துவது\n	• பெரிய கோப்புகளைப் பதிவிறக்குவது/பதிவேற்றுவது\n	• அதிக வெப்பநிலையில் ஃபோனைப் பயன்படுத்துவது"</string>
     <string name="high_temp_title" msgid="4589508026407318374">"மொபைல் சூடாகிறது"</string>
     <string name="high_temp_notif_message" msgid="5642466103153429279">"மொபைலின் வெப்ப அளவு குறையும் போது, சில அம்சங்களைப் பயன்படுத்த முடியாது"</string>
     <string name="high_temp_dialog_message" msgid="6840700639374113553">"உங்கள் மொபைலின் வெப்ப அளவு தானாகவே குறையும். தொடர்ந்து நீங்கள் மொபைலைப் பயன்படுத்தலாம், ஆனால் அதன் வேகம் குறைவாக இருக்கக்கூடும்.\n\nமொபைலின் வெப்ப அளவு குறைந்தவுடன், அது இயல்பு நிலையில் இயங்கும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 350ccc6..62fe95b 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"రద్దు చేయి"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"నిర్ధారించు"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"మళ్లీ ప్రయత్నించు"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"ఖాళీ ప్రదేశం, ప్రామాణీకరణను రద్దు చేయడానికి నొక్కండి"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"ప్రామాణీకరణను రద్దు చేయడానికి నొక్కండి"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"దయచేసి మళ్ళీ ప్రయత్నించండి"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"మీ ముఖాన్ని క్యాప్చర్ చేస్తోంది"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ముఖం ప్రామాణీకరించబడింది"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"నిర్ధారించబడింది"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"పూర్తి చేయడానికి \"నిర్ధారించు\" నొక్కండి"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ప్రామాణీకరించబడింది"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"వేలిముద్ర సెన్సార్‌ను తాకండి"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"వేలిముద్ర చిహ్నం"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"మీ కోసం చూస్తోంది…"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 9d39859..eb6810a0 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"ยกเลิก"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"ยืนยัน"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"ลองอีกครั้ง"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"พื้นที่ว่าง แตะเพื่อยกเลิกการตรวจสอบสิทธิ์"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"แตะเพื่อยกเลิกการตรวจสอบสิทธิ์"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"โปรดลองอีกครั้ง"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"กำลังมองหาใบหน้าของคุณ"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"ตรวจสอบสิทธิ์ใบหน้าแล้ว"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"ยืนยันแล้ว"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"แตะยืนยันเพื่อดำเนินการให้เสร็จสมบูรณ์"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"ตรวจสอบสิทธิ์แล้ว"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"แตะเซ็นเซอร์ลายนิ้วมือ"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"ไอคอนลายนิ้วมือ"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"กำลังหาใบหน้าคุณ…"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 56da232..f6abdcb 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Kanselahin"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Kumpirmahin"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Subukang muli"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Walang laman na rehiyon, mag-tap para kanselahin ang pag-authenticate"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"I-tap para kanselahin ang pag-authenticate"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Pakisubukan ulit"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Hinahanap ang iyong mukha"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Na-authenticate ang mukha"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Nakumpirma"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"I-tap ang Kumpirmahin para kumpletuhin"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Na-authenticate"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Pindutin ang fingerprint sensor"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Icon ng fingerprint"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Hinahanap ka…"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 257b9de..b5e1702 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"İptal"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Onaylayın"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Tekrar dene"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Boş alan, yetkilendirmeyi iptal etmek için dokunun"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Kimlik doğrulama işlemini iptal etmek için dokunun"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Lütfen tekrar deneyin"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Yüzünüz aranıyor"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Yüz kimliği doğrulandı"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Onaylandı"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tamamlamak için Onayla\'ya dokunun"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Kimliği Doğrulandı"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Parmak izi sensörüne dokunun"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Parmak izi simgesi"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Yüzünüz tanınmaya çalışılıyor…"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index d82acf7..36bec1e 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Скасувати"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Підтвердити"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Повторити спробу"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Порожнє місце, торкніться, щоб скасувати автентифікацію"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Натисніть, щоб скасувати автентифікацію"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Повторіть спробу"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Триває розпізнавання обличчя"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Обличчя автентифіковано"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Підтверджено"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Щоб завершити, натисніть \"Підтвердити\""</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Автентифіковано"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Торкніться сканера відбитків пальців"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Значок відбитка пальця"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Пошук обличчя…"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 7d4f177..5fceabe 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"منسوخ کریں"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"تصدیق کریں"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"دوبارہ کوشش کریں"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"خالی جگہیں، تصدیق کو منسوخ کرنے کے لیے تھپتھپائیں"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"تصدیق کو منسوخ کرنے کے لیے تھپتھپائیں"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"براہ کرم دوبارہ کوشش کریں"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"آپ کا چہرہ تلاش کیا جا رہا ہے"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"چہرے کی تصدیق ہو گئی"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"تصدیق شدہ"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"مکمل کرنے کیلئے \'تصدیق کریں\' تھپتھپائیں"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"تصدیق کردہ"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"فنگر پرنٹ سینسر پر ٹچ کریں"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"فنگر پرنٹ آئیکن"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"آپ کے لیے تلاش کیا جا رہا ہے…"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e4a4548..37651a5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -60,7 +60,7 @@
     <string name="usb_debugging_always" msgid="303335496705863070">"Doimo ushbu kompyuterdan ruxsat berilsin"</string>
     <string name="usb_debugging_allow" msgid="2272145052073254852">"Ruxsat berish"</string>
     <string name="usb_debugging_secondary_user_title" msgid="6353808721761220421">"USB orqali nosozliklarni tuzatishga ruxsat berilmagan"</string>
-    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni tuzatish funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga o‘ting."</string>
+    <string name="usb_debugging_secondary_user_message" msgid="6067122453571699801">"Ayni paytda ushbu qurilmaga o‘z hisobi bilan kirgan foydalanuvchi USB orqali nosozliklarni aniqlash funksiyasini yoqa olmaydi. Bu funksiyadan foydalanish uchun asosiy foydalanuvchi profiliga o‘ting."</string>
     <string name="usb_contaminant_title" msgid="206854874263058490">"USB port faolsizlashtirildi"</string>
     <string name="usb_contaminant_message" msgid="7379089091591609111">"Qurilmangizni suyuqlik va turli parchalardan himoya qilish uchun USB port faolsizlashtiriladi va hech qanday aksessuarni aniqlay olmaydi.\n\nUSB portdan xavfsiz foydalanish mumkin boʻlganda, sizga xabar beriladi."</string>
     <string name="usb_port_enabled" msgid="7906141351687694867">"Quvvatlash moslamalari va aksessuarlarni aniqlash uchun USB port yoqildi"</string>
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Bekor qilish"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"OK"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Qayta urinish"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Boʻsh hudud, tekshiruvni bekor qilish uchun bosing"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Tekshiruvni bekor qilish uchun bosing"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Qayta urining"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Yuz tekshirilmoqda"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Yuzingiz aniqlandi"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Tasdiqlangan"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Tasdiqlash uchun tegining"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Tasdiqlandi"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Barmoq izi skaneriga tegining"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Barmoq izi belgisi"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Yuzingiz tekshirilmoqda…"</string>
@@ -401,7 +402,7 @@
     <string name="speed_bump_explanation" msgid="1288875699658819755">"Kam ahamiyatli bildirishnomalarni pastda ko‘rsatish"</string>
     <string name="notification_tap_again" msgid="7590196980943943842">"Ochish uchun yana bosing"</string>
     <string name="keyguard_unlock" msgid="6035822649218712063">"Ochish uchun tepaga suring"</string>
-    <string name="keyguard_retry" msgid="5221600879614948709">"Tepaga suring va qayta urining"</string>
+    <string name="keyguard_retry" msgid="5221600879614948709">"Qayta urinish uchun tepaga suring"</string>
     <string name="do_disclosure_generic" msgid="5615898451805157556">"Bu – tashkilotingiz tomonidan boshqariladigan qurilma"</string>
     <string name="do_disclosure_with_name" msgid="5640615509915445501">"Bu – <xliff:g id="ORGANIZATION_NAME">%s</xliff:g> tomonidan boshqariladigan qurilma"</string>
     <string name="phone_hint" msgid="4872890986869209950">"Telefonni ochish uchun suring"</string>
@@ -459,7 +460,7 @@
     <string name="media_projection_remember_text" msgid="3103510882172746752">"Boshqa ko‘rsatilmasin"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"Hammasini tozalash"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"Boshqarish"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Ovozsiz bildirishnomalar"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"Sokin bildirishnomalar"</string>
     <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"Barcha tovushsiz bildirishnomalarni tozalash"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilingan"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"Boshlash"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index d3caa2b..f678265 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Hủy"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Xác nhận"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Thử lại"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Vùng trống, nhấn để hủy quá trình xác thực"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Nhấn để hủy quá trình xác thực"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Vui lòng thử lại"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Đang tìm khuôn mặt của bạn"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Đã xác thực khuôn mặt"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Ðã xác nhận"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Nhấn vào Xác nhận để hoàn tất"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Đã xác thực"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Chạm vào cảm biến vân tay"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Biểu tượng vân tay"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Đang tìm kiếm bạn…"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index ca72b08..05509f6 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"确认"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"重试"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白区域,点按即可取消身份验证"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"点按即可取消身份验证"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"请重试"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在查找您的面孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"面孔身份验证成功"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"已确认"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"点按“确认”即可完成"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"已经过身份验证"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"请触摸指纹传感器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指纹图标"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在查找您的面孔…"</string>
@@ -376,7 +377,7 @@
     <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4453017157391574402">"在日出时关闭"</string>
     <string name="quick_settings_night_secondary_label_on_at" msgid="6256314040368487637">"在<xliff:g id="TIME">%s</xliff:g> 开启"</string>
     <string name="quick_settings_secondary_label_until" msgid="2749196569462600150">"直到<xliff:g id="TIME">%s</xliff:g>"</string>
-    <string name="quick_settings_ui_mode_night_label" msgid="3419947801072692538">"深色主题背景"</string>
+    <string name="quick_settings_ui_mode_night_label" msgid="3419947801072692538">"深色主题"</string>
     <string name="quick_settings_ui_mode_night_label_battery_saver" msgid="7438725724589758362">"深色主题背景\n省电模式"</string>
     <string name="quick_settings_nfc_label" msgid="9012153754816969325">"NFC"</string>
     <string name="quick_settings_nfc_off" msgid="6883274004315134333">"NFC 已停用"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 128e1ca..b57e61f 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"請再試一次"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白區域,輕按即可取消驗證"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"輕按即可取消驗證"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"請再試一次"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在尋找您的臉孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"臉孔已經驗證"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"已確認"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"輕按 [確定] 以完成"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"驗證咗"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"請輕觸指紋感應器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋圖示"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在搜尋您的臉孔…"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 6bacf70..14fedbb 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"取消"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"確認"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"再試一次"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"空白的區域,輕觸即可取消驗證"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"輕觸即可取消驗證"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"請再試一次"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"正在尋找你的臉孔"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"臉孔驗證成功"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"確認完畢"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"輕觸 [確認] 完成驗證設定"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"已通過驗證"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"請輕觸指紋感應器"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"指紋圖示"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"正在尋找你的臉孔…"</string>
@@ -459,8 +460,8 @@
     <string name="media_projection_remember_text" msgid="3103510882172746752">"不要再顯示"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"全部清除"</string>
     <string name="manage_notifications_text" msgid="2386728145475108753">"管理"</string>
-    <string name="notification_section_header_gentle" msgid="4372438504154095677">"無聲通知"</string>
-    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有無聲通知"</string>
+    <string name="notification_section_header_gentle" msgid="4372438504154095677">"靜音通知"</string>
+    <string name="accessibility_notification_section_header_gentle_clear_all" msgid="4286716295850400959">"清除所有靜音通知"</string>
     <string name="dnd_suppressing_shade_text" msgid="1904574852846769301">"「零打擾」模式已將通知設為暫停"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"立即開始"</string>
     <string name="empty_shade_text" msgid="708135716272867002">"沒有通知"</string>
@@ -645,7 +646,7 @@
     <string name="inline_block_button" msgid="8735843688021655065">"封鎖"</string>
     <string name="inline_keep_button" msgid="6665940297019018232">"繼續顯示"</string>
     <string name="inline_minimize_button" msgid="966233327974702195">"最小化"</string>
-    <string name="inline_silent_button_silent" msgid="5315879183296940969">"無聲"</string>
+    <string name="inline_silent_button_silent" msgid="5315879183296940969">"靜音"</string>
     <string name="inline_silent_button_stay_silent" msgid="6308371431217601009">"繼續顯示通知但不發出音效"</string>
     <string name="inline_silent_button_alert" msgid="6008435419895088034">"快訊"</string>
     <string name="inline_silent_button_keep_alerting" msgid="327696842264359693">"繼續顯示通知"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 2306e4a..c158c77 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -119,12 +119,13 @@
     <string name="cancel" msgid="6442560571259935130">"Khansela"</string>
     <string name="biometric_dialog_confirm" msgid="6468457350041712674">"Qinisekisa"</string>
     <string name="biometric_dialog_try_again" msgid="1900185172633183201">"Zama futhi"</string>
-    <string name="biometric_dialog_empty_space_description" msgid="7997936968009073717">"Isifunda esingenalutho, thepha ukuze ukhansele ukufakazela ubuqiniso"</string>
+    <string name="biometric_dialog_empty_space_description" msgid="6337699671577692511">"Thepha ukuze ukhansele ukufakazela ubuqiniso"</string>
     <string name="biometric_dialog_face_icon_description_idle" msgid="4497694707475970790">"Sicela uzame futhi"</string>
     <string name="biometric_dialog_face_icon_description_authenticating" msgid="4512727754496228488">"Ifuna ubuso bakho"</string>
     <string name="biometric_dialog_face_icon_description_authenticated" msgid="595380451325425259">"Ubuso bufakazelwe ubuqiniso"</string>
     <string name="biometric_dialog_face_icon_description_confirmed" msgid="2003141400387093967">"Kuqinisekisiwe"</string>
     <string name="biometric_dialog_tap_confirm" msgid="4540715260292022404">"Thepha okuthi Qinisekisa ukuze uqedele"</string>
+    <string name="biometric_dialog_authenticated" msgid="5918352844999713693">"Kugunyaziwe"</string>
     <string name="fingerprint_dialog_touch_sensor" msgid="8511557690663181761">"Thinta inzwa yesigxivizo somunwe"</string>
     <string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="3125122495414253226">"Isithonjana sezigxivizo zeminwe"</string>
     <string name="face_dialog_looking_for_face" msgid="7049276266074494689">"Kufunwa wena…"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 61816f6..bda1c52 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -204,4 +204,6 @@
 
     <color name="GM2_yellow_500">#FFFBBC04</color>
     <color name="GM2_green_500">#FF34A853</color>
+    <color name="GM2_blue_500">#FF4285F4</color>
+
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 6e8e823..61210d3 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -273,7 +273,6 @@
 
     <!-- SystemUI Services: The classes of the stuff to start. -->
     <string-array name="config_systemUIServiceComponents" translatable="false">
-        <item>com.android.systemui.Dependency$DependencyCreator</item>
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
@@ -292,7 +291,7 @@
         <item>com.android.systemui.LatencyTester</item>
         <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
         <item>com.android.systemui.ScreenDecorations</item>
-        <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
+        <item>com.android.systemui.biometrics.AuthController</item>
         <item>com.android.systemui.SliceBroadcastRelayHandler</item>
         <item>com.android.systemui.SizeCompatModeActivityController</item>
         <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
@@ -304,7 +303,6 @@
 
     <!-- SystemUI Services (per user): The classes of the stuff to start for each user. This is a subset of the config_systemUIServiceComponents -->
     <string-array name="config_systemUIServiceComponentsPerUser" translatable="false">
-        <item>com.android.systemui.Dependency$DependencyCreator</item>
         <item>com.android.systemui.util.NotificationChannels</item>
     </string-array>
 
@@ -482,4 +480,7 @@
     <!-- Preferred refresh rate at keyguard, if supported by the display -->
     <integer name="config_keyguardRefreshRate">-1</integer>
 
+    <!-- Whether or not to add a "people" notifications section -->
+    <bool name="config_usePeopleFiltering">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index be815e1..1079206 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -457,6 +457,7 @@
     <dimen name="qs_page_indicator_width">16dp</dimen>
     <dimen name="qs_page_indicator_height">8dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
+    <dimen name="qs_tile_detail_padding">3dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
     <dimen name="qs_tile_divider_height">1dp</dimen>
     <dimen name="qs_panel_padding">16dp</dimen>
@@ -1010,6 +1011,8 @@
     <dimen name="biometric_dialog_corner_size">4dp</dimen>
     <dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
     <dimen name="biometric_dialog_border_padding">4dp</dimen>
+    <dimen name="biometric_dialog_elevation">1dp</dimen>
+    <dimen name="biometric_dialog_icon_padding">16dp</dimen>
 
     <!-- Wireless Charging Animation values -->
     <dimen name="wireless_charging_dots_radius_start">0dp</dimen>
@@ -1102,18 +1105,23 @@
     <dimen name="bubble_flyout_pointer_size">6dp</dimen>
     <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
     <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
-    <!-- Padding around a collapsed bubble -->
-    <dimen name="bubble_view_padding">0dp</dimen>
-    <!-- Padding between bubbles when displayed in expanded state -->
-    <dimen name="bubble_padding">8dp</dimen>
+    <!-- Padding between status bar and bubbles when displayed in expanded state -->
+    <dimen name="bubble_padding_top">16dp</dimen>
     <!-- Size of individual bubbles. -->
-    <dimen name="individual_bubble_size">52dp</dimen>
+    <dimen name="individual_bubble_size">60dp</dimen>
+    <!-- Size of bubble icon bitmap. -->
+    <dimen name="bubble_icon_bitmap_size">52dp</dimen>
+    <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
+    <dimen name="bubble_touch_padding">12dp</dimen>
     <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
-    <dimen name="bubble_dismiss_encircle_size">56dp</dimen>
+    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
     <!-- How much to inset the icon in the circle -->
     <dimen name="bubble_icon_inset">16dp</dimen>
     <!-- Padding around the view displayed when the bubble is expanded -->
     <dimen name="bubble_expanded_view_padding">4dp</dimen>
+    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+         a slight touch slop around the expanded view. -->
+    <dimen name="bubble_expanded_view_slop">8dp</dimen>
     <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
     <dimen name="bubble_expanded_default_height">180dp</dimen>
     <!-- Height of the triangle that points to the expanded bubble -->
@@ -1122,10 +1130,8 @@
     <dimen name="bubble_pointer_width">6dp</dimen>
     <!-- Extra padding around the dismiss target for bubbles -->
     <dimen name="bubble_dismiss_slop">16dp</dimen>
-    <!-- Height of the header within the expanded view. -->
-    <dimen name="bubble_expanded_header_height">48dp</dimen>
-    <!-- Left and right padding applied to the header. -->
-    <dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+    <!-- Height of button allowing users to adjust settings for bubbles. -->
+    <dimen name="bubble_settings_size">48dp</dimen>
     <!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
     <dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
     <!-- How far, vertically, to animate the expanded view over when animating in/out. -->
@@ -1138,12 +1144,10 @@
     <dimen name="bubble_message_padding">4dp</dimen>
     <!-- Offset between bubbles in their stacked position. -->
     <dimen name="bubble_stack_offset">5dp</dimen>
-    <!-- How far offscreen the bubble stack rests. -->
-    <dimen name="bubble_stack_offscreen">5dp</dimen>
+    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
+    <dimen name="bubble_stack_offscreen">9dp</dimen>
     <!-- How far down the screen the stack starts. -->
-    <dimen name="bubble_stack_starting_offset_y">100dp</dimen>
-    <!-- Size of image buttons in the bubble header -->
-    <dimen name="bubble_header_icon_size">48dp</dimen>
+    <dimen name="bubble_stack_starting_offset_y">96dp</dimen>
     <!-- Space between the pointer triangle and the bubble expanded view -->
     <dimen name="bubble_pointer_margin">8dp</dimen>
     <!-- Height of the permission prompt shown with bubbles -->
@@ -1152,6 +1156,7 @@
          snap to the dismiss target. -->
     <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
     <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 66f1949..3727181 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -137,6 +137,7 @@
     <item type="id" name="scale_x_dynamicanimation_tag"/>
     <item type="id" name="scale_y_dynamicanimation_tag"/>
     <item type="id" name="physics_animator_tag"/>
+    <item type="id" name="target_animator_tag" />
 
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 19e682b..97e2f0f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -294,7 +294,7 @@
     <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] -->
     <string name="biometric_dialog_try_again">Try again</string>
     <!-- Content description for empty spaces that are not taken by the biometric dialog. Clicking on these areas will cancel authentication and dismiss the biometric dialog [CHAR LIMIT=NONE] -->
-    <string name="biometric_dialog_empty_space_description">Empty region, tap to cancel authentication</string>
+    <string name="biometric_dialog_empty_space_description">Tap to cancel authentication</string>
     <!-- Content description for the face icon when the device is not authenticating anymore [CHAR LIMIT=NONE] -->
     <string name="biometric_dialog_face_icon_description_idle">Please try again</string>
     <!-- Content description for the face icon when the device is authenticating [CHAR LIMIT=NONE] -->
@@ -305,6 +305,8 @@
     <string name="biometric_dialog_face_icon_description_confirmed">Confirmed</string>
     <!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
     <string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
+    <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] -->
+    <string name="biometric_dialog_authenticated">Authenticated</string>
 
     <!-- Message shown when the system-provided fingerprint dialog is shown, asking for authentication -->
     <string name="fingerprint_dialog_touch_sensor">Touch the fingerprint sensor</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 82287873..00e8b53 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -225,14 +225,16 @@
                 runner = new IRecentsAnimationRunner.Stub() {
                     @Override
                     public void onAnimationStart(IRecentsAnimationController controller,
-                            RemoteAnimationTarget[] apps, Rect homeContentInsets,
-                            Rect minimizedHomeBounds) {
+                            RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+                            Rect homeContentInsets, Rect minimizedHomeBounds) {
                         final RecentsAnimationControllerCompat controllerCompat =
                                 new RecentsAnimationControllerCompat(controller);
                         final RemoteAnimationTargetCompat[] appsCompat =
                                 RemoteAnimationTargetCompat.wrap(apps);
+                        final RemoteAnimationTargetCompat[] wallpapersCompat =
+                                RemoteAnimationTargetCompat.wrap(wallpapers);
                         animationHandler.onAnimationStart(controllerCompat, appsCompat,
-                                homeContentInsets, minimizedHomeBounds);
+                                wallpapersCompat, homeContentInsets, minimizedHomeBounds);
                     }
 
                     @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
index 3ae2df5b..2797042 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PinnedStackListenerForwarder.java
@@ -16,9 +16,10 @@
 
 package com.android.systemui.shared.system;
 
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Rect;
-import android.os.RemoteException;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
@@ -32,62 +33,132 @@
  * previously set listener.
  */
 public class PinnedStackListenerForwarder extends IPinnedStackListener.Stub {
-    private List<IPinnedStackListener> mListeners = new ArrayList<>();
+    private List<PinnedStackListener> mListeners = new ArrayList<>();
 
     /** Adds a listener to receive updates from the WindowManagerService. */
-    public void addListener(IPinnedStackListener listener) {
+    public void addListener(PinnedStackListener listener) {
         mListeners.add(listener);
     }
 
     /** Removes a listener so it will no longer receive updates from the WindowManagerService. */
-    public void removeListener(IPinnedStackListener listener) {
+    public void removeListener(PinnedStackListener listener) {
         mListeners.remove(listener);
     }
 
     @Override
-    public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onListenerRegistered(IPinnedStackController controller) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onListenerRegistered(controller);
         }
     }
 
     @Override
-    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds,
-            boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
-            listener.onMovementBoundsChanged(
-                    insetBounds, normalBounds, animatingBounds,
-                    fromImeAdjustment, fromShelfAdjustment, displayRotation);
+    public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+            boolean fromShelfAdjustment) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onMovementBoundsChanged(animatingBounds, fromImeAdjustment,
+                    fromShelfAdjustment);
         }
     }
 
     @Override
-    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onImeVisibilityChanged(imeVisible, imeHeight);
         }
     }
 
     @Override
-    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-            throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onShelfVisibilityChanged(shelfVisible, shelfHeight);
         }
     }
 
     @Override
-    public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onMinimizedStateChanged(boolean isMinimized) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onMinimizedStateChanged(isMinimized);
         }
     }
 
     @Override
-    public void onActionsChanged(ParceledListSlice actions) throws RemoteException {
-        for (IPinnedStackListener listener : mListeners) {
+    public void onActionsChanged(ParceledListSlice actions) {
+        for (PinnedStackListener listener : mListeners) {
             listener.onActionsChanged(actions);
         }
     }
+
+    @Override
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onSaveReentrySnapFraction(componentName, bounds);
+        }
+    }
+
+    @Override
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onResetReentrySnapFraction(componentName);
+        }
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onDisplayInfoChanged(displayInfo);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged() {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onConfigurationChanged();
+        }
+    }
+
+    @Override
+    public void onAspectRatioChanged(float aspectRatio) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onAspectRatioChanged(aspectRatio);
+        }
+    }
+
+    @Override
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        for (PinnedStackListener listener : mListeners) {
+            listener.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
+        }
+    }
+
+    /**
+     * A counterpart of {@link IPinnedStackListener} with empty implementations.
+     * Subclasses can ignore those methods they do not intend to take action upon.
+     */
+    public static class PinnedStackListener {
+        public void onListenerRegistered(IPinnedStackController controller) {}
+
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {}
+
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+
+        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
+
+        public void onMinimizedStateChanged(boolean isMinimized) {}
+
+        public void onActionsChanged(ParceledListSlice actions) {}
+
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {}
+
+        public void onResetReentrySnapFraction(ComponentName componentName) {}
+
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {}
+
+        public void onConfigurationChanged() {}
+
+        public void onAspectRatioChanged(float aspectRatio) {}
+
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {}
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 579858a..2c99c5c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -21,12 +21,12 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 public interface RecentsAnimationListener {
-
     /**
      * Called when the animation into Recents can start. This call is made on the binder thread.
      */
     void onAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds);
+            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
+            Rect homeContentInsets, Rect minimizedHomeBounds);
 
     /**
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 61be076..02e509e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -45,9 +45,12 @@
         return new IRemoteAnimationRunner.Stub() {
             @Override
             public void onAnimationStart(RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers,
                     final IRemoteAnimationFinishedCallback finishedCallback) {
                 final RemoteAnimationTargetCompat[] appsCompat =
                         RemoteAnimationTargetCompat.wrap(apps);
+                final RemoteAnimationTargetCompat[] wallpapersCompat =
+                        RemoteAnimationTargetCompat.wrap(wallpapers);
                 final Runnable animationFinishedCallback = new Runnable() {
                     @Override
                     public void run() {
@@ -59,7 +62,8 @@
                         }
                     }
                 };
-                remoteAnimationAdapter.onAnimationStart(appsCompat, animationFinishedCallback);
+                remoteAnimationAdapter.onAnimationStart(appsCompat, wallpapersCompat,
+                        animationFinishedCallback);
             }
 
             @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5a85df9..33372f6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.system;
 
 public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(RemoteAnimationTargetCompat[] apps, Runnable finishedCallback);
+    void onAnimationStart(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, Runnable finishedCallback);
     void onAnimationCancelled();
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 221782e..ca88f13 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -68,7 +68,7 @@
 
     public static RemoteAnimationTargetCompat[] wrap(RemoteAnimationTarget[] apps) {
         final RemoteAnimationTargetCompat[] appsCompat =
-                new RemoteAnimationTargetCompat[apps.length];
+                new RemoteAnimationTargetCompat[apps != null ? apps.length : 0];
         for (int i = 0; i < apps.length; i++) {
             appsCompat[i] = new RemoteAnimationTargetCompat(apps[i]);
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index 9ba21a3..e80b437 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -142,7 +142,9 @@
     public static void applyParams(TransactionCompat t,
             SyncRtSurfaceTransactionApplierCompat.SurfaceParams params) {
         t.setMatrix(params.surface, params.matrix);
-        t.setWindowCrop(params.surface, params.windowCrop);
+        if (params.windowCrop != null) {
+            t.setWindowCrop(params.surface, params.windowCrop);
+        }
         t.setAlpha(params.surface, params.alpha);
         t.setLayer(params.surface, params.layer);
         t.setCornerRadius(params.surface, params.cornerRadius);
@@ -187,14 +189,14 @@
          * @param surface The surface to modify.
          * @param alpha Alpha to apply.
          * @param matrix Matrix to apply.
-         * @param windowCrop Crop to apply.
+         * @param windowCrop Crop to apply, only applied if not {@code null}
          */
         public SurfaceParams(SurfaceControlCompat surface, float alpha, Matrix matrix,
                 Rect windowCrop, int layer, float cornerRadius) {
             this.surface = surface;
             this.alpha = alpha;
             this.matrix = new Matrix(matrix);
-            this.windowCrop = new Rect(windowCrop);
+            this.windowCrop = windowCrop != null ? new Rect(windowCrop) : null;
             this.layer = layer;
             this.cornerRadius = cornerRadius;
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 7757161..5ddf89c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -72,6 +72,13 @@
      */
     public void onSingleTaskDisplayDrawn(int displayId) { }
 
+    /**
+     * Called when the last task is removed from a display which can only contain one task.
+     *
+     * @param displayId the id of the display from which the window is removed.
+     */
+    public void onSingleTaskDisplayEmpty(int displayId) {}
+
     public void onTaskProfileLocked(int taskId, int userId) { }
     public void onTaskCreated(int taskId, ComponentName componentName) { }
     public void onTaskRemoved(int taskId) { }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index a7f4396..820057a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -209,6 +209,12 @@
     }
 
     @Override
+    public void onSingleTaskDisplayEmpty(int displayId) throws RemoteException {
+        mHandler.obtainMessage(H.ON_SINGLE_TASK_DISPLAY_EMPTY, displayId,
+                0 /* unused */).sendToTarget();
+    }
+
+    @Override
     public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
         mHandler.obtainMessage(H.ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget();
     }
@@ -240,6 +246,7 @@
         private static final int ON_SINGLE_TASK_DISPLAY_DRAWN = 19;
         private static final int ON_TASK_DISPLAY_CHANGED = 20;
         private static final int ON_TASK_LIST_UPDATED = 21;
+        private static final int ON_SINGLE_TASK_DISPLAY_EMPTY = 22;
 
 
         public H(Looper looper) {
@@ -382,6 +389,13 @@
                         }
                         break;
                     }
+                    case ON_SINGLE_TASK_DISPLAY_EMPTY: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onSingleTaskDisplayEmpty(
+                                    msg.arg1);
+                        }
+                        break;
+                    }
                     case ON_TASK_DISPLAY_CHANGED: {
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
                             mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 794c30a..9f1a1fa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -27,12 +27,12 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.IPinnedStackListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 
 public class WindowManagerWrapper {
 
@@ -212,7 +212,7 @@
      * Adds a pinned stack listener, which will receive updates from the window manager service
      * along with any other pinned stack listeners that were added via this method.
      */
-    public void addPinnedStackListener(IPinnedStackListener listener) throws RemoteException {
+    public void addPinnedStackListener(PinnedStackListener listener) throws RemoteException {
         mPinnedStackListenerForwarder.addListener(listener);
         WindowManagerGlobal.getWindowManagerService().registerPinnedStackListener(
                 DEFAULT_DISPLAY, mPinnedStackListenerForwarder);
@@ -221,7 +221,7 @@
     /**
      * Removes a pinned stack listener.
      */
-    public void removePinnedStackListener(IPinnedStackListener listener) {
+    public void removePinnedStackListener(PinnedStackListener listener) {
         mPinnedStackListenerForwarder.removeListener(listener);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index adcb7a1..f6b03c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -24,6 +24,9 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
 import java.util.Locale;
 
 public class CarrierText extends TextView {
@@ -83,7 +86,7 @@
                 com.android.internal.R.string.kg_text_message_separator);
         mCarrierTextController = new CarrierTextController(mContext, mSeparator, mShowAirplaneMode,
                 mShowMissingSim);
-        mShouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        mShouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setSelected(mShouldMarquee); // Allow marquee to work.
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 107d5cc..e1b723e 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
 import java.util.List;
@@ -225,7 +226,7 @@
             // TODO(b/140034799)
             if (whitelistIpcs(() -> ConnectivityManager.from(mContext).isNetworkSupported(
                     ConnectivityManager.TYPE_MOBILE))) {
-                mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+                mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
                 mKeyguardUpdateMonitor.registerCallback(mCallback);
                 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
                 telephonyManager.listen(mPhoneStateListener,
@@ -298,7 +299,9 @@
                 }
             }
         }
-        if (allSimsMissing) {
+        // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
+        // This condition will also be true always when numSubs == 0
+        if (allSimsMissing && !anySimReadyAndInService) {
             if (numSubs != 0) {
                 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable.
                 // This depends on mPlmn containing the text "Emergency calls only" when the radio
@@ -487,7 +490,7 @@
         }
 
         final boolean missingAndNotProvisioned =
-                !KeyguardUpdateMonitor.getInstance(mContext).isDeviceProvisioned()
+                !Dependency.get(KeyguardUpdateMonitor.class).isDeviceProvisioned()
                         && (simState == IccCardConstants.State.ABSENT
                         || simState == IccCardConstants.State.PERM_DISABLED);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 979f3dc..1c30762 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -40,6 +40,7 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
 import com.android.systemui.util.EmergencyDialerConstants;
 
 /**
@@ -104,13 +105,13 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
     }
 
     @Override
@@ -186,7 +187,7 @@
                 mEmergencyButtonCallback.onEmergencyButtonClickedWhenInCall();
             }
         } else {
-            KeyguardUpdateMonitor.getInstance(mContext).reportEmergencyCallAction(
+            Dependency.get(KeyguardUpdateMonitor.class).reportEmergencyCallAction(
                     true /* bypassHandler */);
             getContext().startActivityAsUser(INTENT_EMERGENCY_DIAL,
                     ActivityOptions.makeCustomAnimation(getContext(), 0, 0).toBundle(),
@@ -201,7 +202,7 @@
             if (isInCall()) {
                 visible = true; // always show "return to call" if phone is off-hook
             } else {
-                final boolean simLocked = KeyguardUpdateMonitor.getInstance(mContext)
+                final boolean simLocked = Dependency.get(KeyguardUpdateMonitor.class)
                         .isSimPinVoiceSecure();
                 if (simLocked) {
                     // Some countries can't handle emergency calls while SIM is locked.
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
index e98ef06..225bebe 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyCarrierArea.java
@@ -21,6 +21,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.R;
+
 public class EmergencyCarrierArea extends AlphaOptimizedLinearLayout {
 
     private CarrierText mCarrierText;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 517abac..d45603f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,6 +33,7 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternChecker;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 import java.util.Arrays;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
index 6a83c71..a78c293 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockAccessibilityDelegate.java
@@ -23,6 +23,8 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.TextView;
 
+import com.android.systemui.R;
+
 /**
  * Replaces fancy colons with regular colons. Only works on TextViews.
  */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 5097216..df0dc46 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -30,6 +30,7 @@
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
 import com.android.keyguard.clock.ClockManager;
 import com.android.systemui.Interpolators;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0ec60e5..9380eb4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.NavigationBarController;
 import com.android.systemui.statusbar.phone.NavigationBarView;
 import com.android.systemui.util.InjectionInflationController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index c2bbfbf..fe64142 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
@@ -32,6 +32,8 @@
 import android.view.WindowManager;
 import android.widget.Button;
 
+import com.android.systemui.R;
+
 /***
  * This button is used by the device with embedded SIM card to disable current carrier to unlock
  * the device with no cellular service.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 14ead04..2c8f238 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -35,6 +35,8 @@
 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.settingslib.Utils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 
 import java.io.File;
@@ -106,7 +108,7 @@
 
     public KeyguardHostView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 037a8d3..a8b1451 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -31,6 +31,8 @@
 import android.view.View;
 import android.widget.TextView;
 
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.lang.ref.WeakReference;
@@ -85,7 +87,7 @@
     @Inject
     public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             ConfigurationController configurationController) {
-        this(context, attrs, KeyguardUpdateMonitor.getInstance(context), configurationController);
+        this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
     }
 
     public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
@@ -177,7 +179,7 @@
 
     @Override
     protected void onFinishInflate() {
-        boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setSelected(shouldMarquee); // This is required to ensure marquee works
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 6808c0f..12ea1d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -24,6 +24,8 @@
 
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 /**
  * Displays a PIN pad for unlocking.
@@ -61,7 +63,7 @@
                         mContext, android.R.interpolator.fast_out_linear_in));
         mDisappearYTranslation = getResources().getDimensionPixelSize(
                 R.dimen.disappear_y_translation);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 96392156..eaaa3ed 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -37,6 +37,7 @@
 import android.widget.TextView.OnEditorActionListener;
 
 import com.android.internal.widget.TextViewInputDisabler;
+import com.android.systemui.R;
 
 import java.util.List;
 /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 56b38f7..297052f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -42,6 +42,8 @@
 import com.android.settingslib.animation.AppearAnimationCreator;
 import com.android.settingslib.animation.AppearAnimationUtils;
 import com.android.settingslib.animation.DisappearAnimationUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 import java.util.List;
 
@@ -116,7 +118,7 @@
 
     public KeyguardPatternView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAppearAnimationUtils = new AppearAnimationUtils(context,
                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index ecafc34..274f739 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -23,6 +23,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.systemui.R;
+
 /**
  * A Pin based Keyguard input view
  */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 454f446..35eb272 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -47,6 +47,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.phone.UnlockMethodCache;
 import com.android.systemui.util.InjectionInflationController;
@@ -129,7 +130,7 @@
         super(context, attrs, defStyle);
         mSecurityModel = new KeyguardSecurityModel(context);
         mLockPatternUtils = new LockPatternUtils(context);
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
         mInjectionInflationController =  new InjectionInflationController(
             SystemUIFactory.getInstance().getRootComponent());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 16e9ffc..bb89959 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
 
 public class KeyguardSecurityModel {
 
@@ -57,7 +58,7 @@
     }
 
     SecurityMode getSecurityMode(int userId) {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                 monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index e6a0250..24da3ad 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -32,6 +32,7 @@
 import android.widget.ViewFlipper;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 /**
  * Subclass of the current view flipper that allows us to overload dispatchTouchEvent() so
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 69da990..9c0a71c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -41,6 +41,8 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 /**
  * Displays a PIN pad for unlocking.
@@ -111,8 +113,8 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_sim_pin_instructions);
         } else {
-            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
-                    getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+                    .getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
             msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
             if (info != null) {
@@ -149,7 +151,7 @@
     }
 
     private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
@@ -222,7 +224,7 @@
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         resetState();
     }
 
@@ -233,7 +235,7 @@
             mSimUnlockProgressDialog.dismiss();
             mSimUnlockProgressDialog = null;
         }
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     /**
@@ -337,7 +339,7 @@
                             resetPasswordText(true /* animate */,
                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
-                                KeyguardUpdateMonitor.getInstance(getContext())
+                                Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
                                 mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 27f71d1..fc888e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -40,6 +40,8 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
 
 
 /**
@@ -171,8 +173,8 @@
         if (count < 2) {
             msg = rez.getString(R.string.kg_puk_enter_puk_hint);
         } else {
-            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
-                    getSubscriptionInfoForSubId(mSubId);
+            SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
+                    .getSubscriptionInfoForSubId(mSubId);
             CharSequence displayName = info != null ? info.getDisplayName() : "";
             msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
             if (info != null) {
@@ -202,7 +204,7 @@
     }
 
     private void handleSubInfoChangeIfNeeded() {
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class);
         int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
         if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
             mSubId = subId;
@@ -271,14 +273,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         resetState();
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     @Override
@@ -408,7 +410,7 @@
                             resetPasswordText(true /* animate */,
                                     result != PhoneConstants.PIN_RESULT_SUCCESS /* announce */);
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
-                                KeyguardUpdateMonitor.getInstance(getContext())
+                                Dependency.get(KeyguardUpdateMonitor.class)
                                         .reportSimUnlocked(mSubId);
                                 mRemainingAttempts = -1;
                                 mShowDefaultMessage = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 37e89c0..63da533 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -40,6 +40,7 @@
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import java.io.FileDescriptor;
@@ -198,7 +199,7 @@
         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
         onSliceContentChanged();
 
-        boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
+        boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
         setEnableMarquee(shouldMarquee);
         refreshFormat();
         updateOwnerInfo();
@@ -314,14 +315,14 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mInfoCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
         Dependency.get(ConfigurationController.class).removeCallback(this);
     }
 
@@ -441,7 +442,7 @@
     }
 
     private boolean shouldShowLogout() {
-        return KeyguardUpdateMonitor.getInstance(mContext).isLogoutEnabled()
+        return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index aa1d8b4..720074b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -39,13 +39,13 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
-import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.UserSwitchObserver;
 import android.app.admin.DevicePolicyManager;
@@ -96,9 +96,9 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
+import com.android.systemui.R;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -115,6 +115,9 @@
 import java.util.TimeZone;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+
 /**
  * Watches for updates that may be interesting to the keyguard, and provides
  * the up to date information as well as a registration for callbacks that care
@@ -214,8 +217,6 @@
         }
     }
 
-    private static KeyguardUpdateMonitor sInstance;
-
     private final Context mContext;
     private final boolean mIsPrimaryUser;
     HashMap<Integer, SimData> mSimDatas = new HashMap<Integer, SimData>();
@@ -292,119 +293,7 @@
         }
     };
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_TIME_UPDATE:
-                    handleTimeUpdate();
-                    break;
-                case MSG_TIMEZONE_UPDATE:
-                    handleTimeZoneUpdate((String) msg.obj);
-                    break;
-                case MSG_BATTERY_UPDATE:
-                    handleBatteryUpdate((BatteryStatus) msg.obj);
-                    break;
-                case MSG_SIM_STATE_CHANGE:
-                    handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
-                    break;
-                case MSG_RINGER_MODE_CHANGED:
-                    handleRingerModeChange(msg.arg1);
-                    break;
-                case MSG_PHONE_STATE_CHANGED:
-                    handlePhoneStateChanged((String) msg.obj);
-                    break;
-                case MSG_DEVICE_PROVISIONED:
-                    handleDeviceProvisioned();
-                    break;
-                case MSG_DPM_STATE_CHANGED:
-                    handleDevicePolicyManagerStateChanged();
-                    break;
-                case MSG_USER_SWITCHING:
-                    handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
-                    break;
-                case MSG_USER_SWITCH_COMPLETE:
-                    handleUserSwitchComplete(msg.arg1);
-                    break;
-                case MSG_KEYGUARD_RESET:
-                    handleKeyguardReset();
-                    break;
-                case MSG_KEYGUARD_BOUNCER_CHANGED:
-                    handleKeyguardBouncerChanged(msg.arg1);
-                    break;
-                case MSG_BOOT_COMPLETED:
-                    handleBootCompleted();
-                    break;
-                case MSG_USER_INFO_CHANGED:
-                    handleUserInfoChanged(msg.arg1);
-                    break;
-                case MSG_REPORT_EMERGENCY_CALL_ACTION:
-                    handleReportEmergencyCallAction();
-                    break;
-                case MSG_STARTED_GOING_TO_SLEEP:
-                    handleStartedGoingToSleep(msg.arg1);
-                    break;
-                case MSG_FINISHED_GOING_TO_SLEEP:
-                    handleFinishedGoingToSleep(msg.arg1);
-                    break;
-                case MSG_STARTED_WAKING_UP:
-                    Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
-                    handleStartedWakingUp();
-                    Trace.endSection();
-                    break;
-                case MSG_FACE_UNLOCK_STATE_CHANGED:
-                    Trace.beginSection(
-                            "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
-                    handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
-                    Trace.endSection();
-                    break;
-                case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
-                    handleSimSubscriptionInfoChanged();
-                    break;
-                case MSG_AIRPLANE_MODE_CHANGED:
-                    handleAirplaneModeChanged();
-                    break;
-                case MSG_SERVICE_STATE_CHANGE:
-                    handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
-                    break;
-                case MSG_SCREEN_TURNED_ON:
-                    handleScreenTurnedOn();
-                    break;
-                case MSG_SCREEN_TURNED_OFF:
-                    Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
-                    handleScreenTurnedOff();
-                    Trace.endSection();
-                    break;
-                case MSG_DREAMING_STATE_CHANGED:
-                    handleDreamingStateChanged(msg.arg1);
-                    break;
-                case MSG_USER_UNLOCKED:
-                    handleUserUnlocked(msg.arg1);
-                    break;
-                case MSG_USER_STOPPED:
-                    handleUserStopped(msg.arg1);
-                    break;
-                case MSG_USER_REMOVED:
-                    handleUserRemoved(msg.arg1);
-                    break;
-                case MSG_ASSISTANT_STACK_CHANGED:
-                    setAssistantVisible((boolean) msg.obj);
-                    break;
-                case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
-                    updateBiometricListeningState();
-                    break;
-                case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
-                    updateLogoutEnabled();
-                    break;
-                case MSG_TELEPHONY_CAPABLE:
-                    updateTelephonyCapable((boolean) msg.obj);
-                    break;
-                default:
-                    super.handleMessage(msg);
-                    break;
-            }
-        }
-    };
+    private final Handler mHandler;
 
     private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray();
     private BiometricManager mBiometricManager;
@@ -446,7 +335,6 @@
 
     private static int sCurrentUser;
     private Runnable mUpdateBiometricListeningState = this::updateBiometricListeningState;
-    private static boolean sDisableHandlerCheckForTesting;
 
     public synchronized static void setCurrentUser(int currentUser) {
         sCurrentUser = currentUser;
@@ -1486,13 +1374,6 @@
         }
     }
 
-    public static KeyguardUpdateMonitor getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new KeyguardUpdateMonitor(context);
-        }
-        return sInstance;
-    }
-
     protected void handleStartedWakingUp() {
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         checkIsHandlerThread();
@@ -1599,12 +1480,127 @@
     }
 
     @VisibleForTesting
-    protected KeyguardUpdateMonitor(Context context) {
+    @Inject
+    protected KeyguardUpdateMonitor(Context context, @Named(MAIN_LOOPER_NAME) Looper mainLooper) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
 
+        mHandler = new Handler(mainLooper) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_TIME_UPDATE:
+                        handleTimeUpdate();
+                        break;
+                    case MSG_TIMEZONE_UPDATE:
+                        handleTimeZoneUpdate((String) msg.obj);
+                        break;
+                    case MSG_BATTERY_UPDATE:
+                        handleBatteryUpdate((BatteryStatus) msg.obj);
+                        break;
+                    case MSG_SIM_STATE_CHANGE:
+                        handleSimStateChange(msg.arg1, msg.arg2, (State) msg.obj);
+                        break;
+                    case MSG_RINGER_MODE_CHANGED:
+                        handleRingerModeChange(msg.arg1);
+                        break;
+                    case MSG_PHONE_STATE_CHANGED:
+                        handlePhoneStateChanged((String) msg.obj);
+                        break;
+                    case MSG_DEVICE_PROVISIONED:
+                        handleDeviceProvisioned();
+                        break;
+                    case MSG_DPM_STATE_CHANGED:
+                        handleDevicePolicyManagerStateChanged();
+                        break;
+                    case MSG_USER_SWITCHING:
+                        handleUserSwitching(msg.arg1, (IRemoteCallback) msg.obj);
+                        break;
+                    case MSG_USER_SWITCH_COMPLETE:
+                        handleUserSwitchComplete(msg.arg1);
+                        break;
+                    case MSG_KEYGUARD_RESET:
+                        handleKeyguardReset();
+                        break;
+                    case MSG_KEYGUARD_BOUNCER_CHANGED:
+                        handleKeyguardBouncerChanged(msg.arg1);
+                        break;
+                    case MSG_BOOT_COMPLETED:
+                        handleBootCompleted();
+                        break;
+                    case MSG_USER_INFO_CHANGED:
+                        handleUserInfoChanged(msg.arg1);
+                        break;
+                    case MSG_REPORT_EMERGENCY_CALL_ACTION:
+                        handleReportEmergencyCallAction();
+                        break;
+                    case MSG_STARTED_GOING_TO_SLEEP:
+                        handleStartedGoingToSleep(msg.arg1);
+                        break;
+                    case MSG_FINISHED_GOING_TO_SLEEP:
+                        handleFinishedGoingToSleep(msg.arg1);
+                        break;
+                    case MSG_STARTED_WAKING_UP:
+                        Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
+                        handleStartedWakingUp();
+                        Trace.endSection();
+                        break;
+                    case MSG_FACE_UNLOCK_STATE_CHANGED:
+                        Trace.beginSection(
+                                "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
+                        handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
+                        Trace.endSection();
+                        break;
+                    case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
+                        handleSimSubscriptionInfoChanged();
+                        break;
+                    case MSG_AIRPLANE_MODE_CHANGED:
+                        handleAirplaneModeChanged();
+                        break;
+                    case MSG_SERVICE_STATE_CHANGE:
+                        handleServiceStateChange(msg.arg1, (ServiceState) msg.obj);
+                        break;
+                    case MSG_SCREEN_TURNED_ON:
+                        handleScreenTurnedOn();
+                        break;
+                    case MSG_SCREEN_TURNED_OFF:
+                        Trace.beginSection("KeyguardUpdateMonitor#handler MSG_SCREEN_TURNED_ON");
+                        handleScreenTurnedOff();
+                        Trace.endSection();
+                        break;
+                    case MSG_DREAMING_STATE_CHANGED:
+                        handleDreamingStateChanged(msg.arg1);
+                        break;
+                    case MSG_USER_UNLOCKED:
+                        handleUserUnlocked(msg.arg1);
+                        break;
+                    case MSG_USER_STOPPED:
+                        handleUserStopped(msg.arg1);
+                        break;
+                    case MSG_USER_REMOVED:
+                        handleUserRemoved(msg.arg1);
+                        break;
+                    case MSG_ASSISTANT_STACK_CHANGED:
+                        setAssistantVisible((boolean) msg.obj);
+                        break;
+                    case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
+                        updateBiometricListeningState();
+                        break;
+                    case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+                        updateLogoutEnabled();
+                        break;
+                    case MSG_TELEPHONY_CAPABLE:
+                        updateTelephonyCapable((boolean) msg.obj);
+                        break;
+                    default:
+                        super.handleMessage(msg);
+                        break;
+                }
+            }
+        };
+
         // Since device can't be un-provisioned, we only need to register a content observer
         // to update mDeviceProvisioned when we are...
         if (!mDeviceProvisioned) {
@@ -1823,7 +1819,10 @@
         return shouldListen;
     }
 
-    private boolean shouldListenForFace() {
+    /**
+     * If face auth is allows to scan on this exact moment.
+     */
+    public boolean shouldListenForFace() {
         final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep;
         final int user = getCurrentUser();
         final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(user);
@@ -2752,35 +2751,12 @@
     }
 
     private void checkIsHandlerThread() {
-        if (sDisableHandlerCheckForTesting) {
-            return;
-        }
         if (!mHandler.getLooper().isCurrentThread()) {
             Log.wtf(TAG, "must call on mHandler's thread "
                     + mHandler.getLooper().getThread() + ", not " + Thread.currentThread());
         }
     }
 
-    /**
-     * Turn off the handler check for testing.
-     *
-     * This is necessary because currently tests are not too careful about which thread they call
-     * into this class on.
-     *
-     * Note that this must be called before scheduling any work involving KeyguardUpdateMonitor
-     * instances.
-     *
-     * TODO: fix the tests and remove this.
-     */
-    @VisibleForTesting
-    public static void disableHandlerCheckForTesting(Instrumentation instrumentation) {
-        Preconditions.checkNotNull(instrumentation, "Must only call this method in tests!");
-        // Don't need synchronization here *if* the callers follow the contract and call this only
-        // before scheduling work for KeyguardUpdateMonitor on other threads, because the scheduling
-        // of that work forces a happens-before relationship.
-        sDisableHandlerCheckForTesting = true;
-    }
-
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
         pw.println("  SIM States:");
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index 1edb57e..b0457fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -29,6 +29,7 @@
 import android.widget.TextView;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.R;
 
 public class NumPadKey extends ViewGroup {
     // list of "ABC", etc per digit, starting with '0'
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index b21bcc9..409ae3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -42,6 +42,8 @@
 import android.view.animation.Interpolator;
 import android.widget.EditText;
 
+import com.android.systemui.R;
+
 import java.util.ArrayList;
 import java.util.Stack;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index 9c5242c..eba2400 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 8e81327..3a2fbe5 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextClock;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
index 7485d33..d44d89e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
@@ -24,7 +24,7 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.keyguard.R;
+import com.android.systemui.R;
 
 /**
  * Positions clock faces (analog, digital, typographic) and handles pixel shifting
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
index 98679ade..c81935a 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
@@ -26,7 +26,7 @@
 import android.widget.TextView;
 
 import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.R;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.ClockPlugin;
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
index 95f1004..34c041b 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
@@ -21,7 +21,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-import com.android.keyguard.R;
+import com.android.systemui.R;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
index 60ca945..b304074 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SmallClockPosition.java
@@ -20,6 +20,7 @@
 import android.util.MathUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
 
 /**
  * Computes preferred position of clock by considering height of status bar and lock icon.
@@ -40,10 +41,10 @@
     private float mDarkAmount;
 
     SmallClockPosition(Resources res) {
-        this(res.getDimensionPixelSize(com.android.keyguard.R.dimen.status_bar_height),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_padding),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.keyguard_lock_height),
-                res.getDimensionPixelSize(com.android.keyguard.R.dimen.burn_in_prevention_offset_y)
+        this(res.getDimensionPixelSize(R.dimen.status_bar_height),
+                res.getDimensionPixelSize(R.dimen.keyguard_lock_padding),
+                res.getDimensionPixelSize(R.dimen.keyguard_lock_height),
+                res.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
         );
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityBinder.java b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
new file mode 100644
index 0000000..2c8a672
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ActivityBinder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.app.Activity;
+
+import com.android.systemui.tuner.TunerActivity;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Services and Activities that are injectable should go here.
+ */
+@Module
+public abstract class ActivityBinder {
+    /** Inject into TunerActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TunerActivity.class)
+    public abstract Activity bindTunerActivity(TunerActivity activity);
+
+    /** Inject into ForegroundServicesDialog. */
+    @Binds
+    @IntoMap
+    @ClassKey(ForegroundServicesDialog.class)
+    public abstract Activity bindForegroundServicesDialog(ForegroundServicesDialog activity);
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceFactory.java b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
similarity index 60%
copy from services/core/java/com/android/server/wm/SurfaceFactory.java
copy to packages/SystemUI/src/com/android/systemui/ComponentBinder.java
index 076b7df..3b35c61 100644
--- a/services/core/java/com/android/server/wm/SurfaceFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/ComponentBinder.java
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.server.wm;
+package com.android.systemui;
 
-import android.view.Surface;
+import dagger.Binds;
+import dagger.Module;
 
 /**
- * Helper class to inject custom {@link Surface} objects into window manager.
+ * Dagger Module that collects related sub-modules together.
  */
-interface SurfaceFactory {
-    Surface make();
-};
-
+@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 62826d8..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,5 +24,11 @@
  */
 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. */
+    SystemUI resolveSystemUI(String className);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
index e8f0196..9952632 100644
--- a/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/ContextComponentResolver.java
@@ -16,23 +16,40 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.Service;
 
 import java.util.Map;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
+import javax.inject.Singleton;
 
 /**
  * Used during Service and Activity instantiation to make them injectable.
  */
+@Singleton
 public class ContextComponentResolver implements ContextComponentHelper {
+    private final Map<Class<?>, Provider<Activity>> mActivityCreators;
     private final Map<Class<?>, Provider<Service>> mServiceCreators;
+    private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators;
 
     @Inject
     ContextComponentResolver(
-            Map<Class<?>, Provider<Service>> serviceCreators) {
+            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);
     }
 
     /**
@@ -43,13 +60,21 @@
         return resolve(className, mServiceCreators);
     }
 
-    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();
-            }
-        }
+    /**
+     * Looks up the SystemUI class name to see if Dagger has an instance of it.
+     */
+    @Override
+    public SystemUI resolveSystemUI(String className) {
+        return resolve(className, mSystemUICreators);
+    }
 
-        return null;
+    private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
+        try {
+            Class<?> clazz = Class.forName(className);
+            Provider<T> provider = creators.get(clazz);
+            return provider == null ? null : provider.get();
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index f9452e2..7771f86 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,8 +15,8 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.NightDisplayListener;
@@ -30,6 +30,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.Preconditions;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.clock.ClockManager;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.appops.AppOpsController;
@@ -110,14 +111,13 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
 import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.AsyncSensorManager;
 import com.android.systemui.util.leak.GarbageMonitor;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.HashMap;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -141,8 +141,11 @@
  * they have no clients they should not have any registered resources like bound
  * services, registered receivers, etc.
  */
-public class Dependency extends SystemUI {
-    private static final String TAG = "Dependency";
+public class Dependency {
+    /**
+     * Key for getting a the main looper.
+     */
+    public static final String MAIN_LOOPER_NAME = "main_looper";
 
     /**
      * Key for getting a background Looper for background work.
@@ -177,6 +180,10 @@
      */
     public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME);
     /**
+     * Key for getting a mainer Looper.
+     */
+    public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME);
+    /**
      * Key for getting a background Handler for background work.
      */
     public static final DependencyKey<Handler> BG_HANDLER = new DependencyKey<>(BG_HANDLER_NAME);
@@ -215,6 +222,7 @@
     @Inject Lazy<UserSwitcherController> mUserSwitcherController;
     @Inject Lazy<UserInfoController> mUserInfoController;
     @Inject Lazy<KeyguardMonitor> mKeyguardMonitor;
+    @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
     @Inject Lazy<BatteryController> mBatteryController;
     @Inject Lazy<NightDisplayListener> mNightDisplayListener;
     @Inject Lazy<ManagedProfileController> mManagedProfileController;
@@ -291,6 +299,7 @@
     @Inject Lazy<PrivacyItemController> mPrivacyItemController;
     @Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
     @Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
+    @Inject @Named(MAIN_LOOPER_NAME) Lazy<Looper> mMainLooper;
     @Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
     @Nullable
@@ -306,18 +315,22 @@
     @Inject Lazy<INotificationManager> mINotificationManager;
     @Inject Lazy<FalsingManager> mFalsingManager;
     @Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
+    @Inject Lazy<AlarmManager> mAlarmManager;
 
     @Inject
     public Dependency() {
     }
 
-    @Override
-    public void start() {
+    /**
+     * Initialize Depenency.
+     */
+    protected void start() {
         // TODO: Think about ways to push these creation rules out of Dependency to cut down
         // on imports.
         mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
         mProviders.put(BG_LOOPER, mBgLooper::get);
         mProviders.put(BG_HANDLER, mBgHandler::get);
+        mProviders.put(MAIN_LOOPER, mMainLooper::get);
         mProviders.put(MAIN_HANDLER, mMainHandler::get);
         mProviders.put(ActivityStarter.class, mActivityStarter::get);
         mProviders.put(ActivityStarterDelegate.class, mActivityStarterDelegate::get);
@@ -344,6 +357,8 @@
 
         mProviders.put(KeyguardMonitor.class, mKeyguardMonitor::get);
 
+        mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
+
         mProviders.put(UserSwitcherController.class, mUserSwitcherController::get);
 
         mProviders.put(UserInfoController.class, mUserInfoController::get);
@@ -485,6 +500,7 @@
         mProviders.put(INotificationManager.class, mINotificationManager::get);
         mProviders.put(FalsingManager.class, mFalsingManager::get);
         mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
+        mProviders.put(AlarmManager.class, mAlarmManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
@@ -495,10 +511,14 @@
         sDependency = this;
     }
 
-    @Override
-    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
+    static void staticDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        sDependency.dump(fd, pw, args);
+    }
 
+    /**
+     * {@see SystemUI.dump}
+     */
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         // Make sure that the DumpController gets added to mDependencies, as they are only added
         // with Dependency#get.
         getDependency(DumpController.class);
@@ -519,9 +539,11 @@
                 .forEach(o -> ((Dumpable) o).dump(fd, pw, args));
     }
 
-    @Override
+    protected static void staticOnConfigurationChanged(Configuration newConfig) {
+        sDependency.onConfigurationChanged(newConfig);
+    }
+
     protected synchronized void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
         mDependencies.values().stream().filter(obj -> obj instanceof ConfigurationChangedReceiver)
                 .forEach(o -> ((ConfigurationChangedReceiver) o).onConfigurationChanged(newConfig));
     }
@@ -575,20 +597,6 @@
     }
 
     /**
-     * Used in separate processes (like tuner settings) to init the dependencies.
-     */
-    public static void initDependencies(Context context) {
-        if (sDependency != null) return;
-        Dependency d = new Dependency();
-        SystemUIFactory.getInstance().getRootComponent()
-                .createDependency()
-                .createSystemUI(d);
-        d.mContext = context;
-        d.mComponents = new HashMap<>();
-        d.start();
-    }
-
-    /**
      * Used in separate process teardown to ensure the context isn't leaked.
      *
      * TODO: Remove once PreferenceFragment doesn't reference getActivity()
@@ -639,15 +647,4 @@
     public interface DependencyInjector {
         void createSystemUI(Dependency dependency);
     }
-
-    public static class DependencyCreator implements Injector {
-        @Override
-        public SystemUI apply(Context context) {
-            Dependency dependency = new Dependency();
-            SystemUIFactory.getInstance().getRootComponent()
-                    .createDependency()
-                    .createSystemUI(dependency);
-            return dependency;
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index 3e2010e..239cbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -19,9 +19,11 @@
 import static com.android.systemui.Dependency.BG_HANDLER_NAME;
 import static com.android.systemui.Dependency.BG_LOOPER_NAME;
 import static com.android.systemui.Dependency.MAIN_HANDLER_NAME;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.app.INotificationManager;
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
@@ -87,6 +89,14 @@
         return thread.getLooper();
     }
 
+    /** Main Looper */
+    @Singleton
+    @Provides
+    @Named(MAIN_LOOPER_NAME)
+    public Looper provideMainLooper() {
+        return Looper.getMainLooper();
+    }
+
     @Singleton
     @Provides
     @Named(BG_HANDLER_NAME)
@@ -97,8 +107,8 @@
     @Singleton
     @Provides
     @Named(MAIN_HANDLER_NAME)
-    public Handler provideMainHandler() {
-        return new Handler(Looper.getMainLooper());
+    public Handler provideMainHandler(@Named(MAIN_LOOPER_NAME) Looper mainLooper) {
+        return new Handler(mainLooper);
     }
 
     @Singleton
@@ -221,4 +231,11 @@
             @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
         return new DeviceProvisionedControllerImpl(context, mainHandler);
     }
+
+    /** */
+    @Singleton
+    @Provides
+    public AlarmManager provideAlarmManager(Context context) {
+        return context.getSystemService(AlarmManager.class);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
new file mode 100644
index 0000000..0e079e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.NotificationLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Extends the lifetime of foreground notification services such that they show for at least
+ * five seconds
+ */
+public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {
+
+    private static final String TAG = "FGSLifetimeExtender";
+    @VisibleForTesting
+    static final int MIN_FGS_TIME_MS = 5000;
+
+    private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
+    private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+
+    public ForegroundServiceLifetimeExtender() {
+    }
+
+    @Override
+    public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
+        mNotificationSafeToRemoveCallback = callback;
+    }
+
+    @Override
+    public boolean shouldExtendLifetime(@NonNull NotificationEntry entry) {
+        if ((entry.notification.getNotification().flags
+                & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
+            return false;
+        }
+
+        long currentTime = System.currentTimeMillis();
+        return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
+    }
+
+    @Override
+    public boolean shouldExtendLifetimeForPendingNotification(
+            @NonNull NotificationEntry entry) {
+        return shouldExtendLifetime(entry);
+    }
+
+    @Override
+    public void setShouldManageLifetime(
+            @NonNull NotificationEntry entry, boolean shouldManage) {
+        if (!shouldManage) {
+            mManagedEntries.remove(entry);
+            return;
+        }
+
+        mManagedEntries.add(entry);
+
+        Runnable r = () -> {
+            if (mManagedEntries.contains(entry)) {
+                mManagedEntries.remove(entry);
+                if (mNotificationSafeToRemoveCallback != null) {
+                    mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
+                }
+            }
+        };
+        mHandler.postDelayed(r, MIN_FGS_TIME_MS);
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 96b62ac..f9d8771 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -50,12 +50,12 @@
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
-                addNotification(entry.notification, entry.importance);
+                addNotification(entry.notification, entry.getImportance());
             }
 
             @Override
             public void onPostEntryUpdated(NotificationEntry entry) {
-                updateNotification(entry.notification, entry.importance);
+                updateNotification(entry.notification, entry.getImportance());
             }
 
             @Override
@@ -66,6 +66,9 @@
                 removeNotification(entry.notification);
             }
         });
+
+        notificationEntryManager.addNotificationLifetimeExtender(
+                new ForegroundServiceLifetimeExtender());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
index cb9523f..710980a 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServicesDialog.java
@@ -44,6 +44,8 @@
 
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * Show a list of currently running foreground services (supplied by the caller)
  * that the user can tap through to their application details.
@@ -72,10 +74,14 @@
                 }
             };
 
+    @Inject
+    ForegroundServicesDialog() {
+        super();
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Dependency.initDependencies(getApplicationContext());
 
         mMetricsLogger = Dependency.get(MetricsLogger.class);
 
diff --git a/packages/SystemUI/src/com/android/systemui/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/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 78a1246..30fbef6 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -24,7 +24,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Map;
-import java.util.function.Function;
 
 public abstract class SystemUI implements SysUiServiceProvider {
     public Context mContext;
@@ -62,7 +61,4 @@
 
         n.addExtras(extras);
     }
-
-    public interface Injector extends Function<Context, SystemUI> {
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 6a59fa1..2c8324c 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.Activity;
 import android.app.Application;
 import android.app.Service;
 import android.content.ContentProvider;
@@ -23,6 +24,7 @@
 import android.content.Intent;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.app.AppComponentFactory;
 
 import javax.inject.Inject;
@@ -38,6 +40,7 @@
  */
 public class SystemUIAppComponentFactory extends AppComponentFactory {
 
+    private static final String TAG = "AppComponentFactory";
     @Inject
     public ContextComponentHelper mComponentHelper;
 
@@ -51,8 +54,8 @@
             @NonNull ClassLoader cl, @NonNull String className)
             throws InstantiationException, IllegalAccessException, ClassNotFoundException {
         Application app = super.instantiateApplicationCompat(cl, className);
-        if (app instanceof ContextProvider) {
-            ((ContextProvider) app).setContextAvailableCallback(
+        if (app instanceof ContextInitializer) {
+            ((ContextInitializer) app).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
                         SystemUIFactory.getInstance().getRootComponent().inject(
@@ -71,8 +74,8 @@
             throws InstantiationException, IllegalAccessException, ClassNotFoundException {
 
         ContentProvider contentProvider = super.instantiateProviderCompat(cl, className);
-        if (contentProvider instanceof ContextProvider) {
-            ((ContextProvider) contentProvider).setContextAvailableCallback(
+        if (contentProvider instanceof ContextInitializer) {
+            ((ContextInitializer) contentProvider).setContextAvailableCallback(
                     context -> {
                         SystemUIFactory.createFromConfig(context);
                         SystemUIFactory.getInstance().getRootComponent().inject(
@@ -86,9 +89,27 @@
 
     @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) {
+            // This shouldn't happen, but does when a device is freshly formatted.
+            // Bug filed against framework to take a look: http://b/141008541
+            SystemUIFactory.getInstance().getRootComponent().inject(
+                    SystemUIAppComponentFactory.this);
+        }
         Service service = mComponentHelper.resolveService(className);
         if (service != null) {
             return service;
@@ -96,11 +117,17 @@
         return super.instantiateServiceCompat(cl, className, intent);
     }
 
-    interface ContextAvailableCallback {
+    /**
+     * A callback that receives a Context when one is ready.
+     */
+    public interface ContextAvailableCallback {
         void onContextAvailable(Context context);
     }
 
-    interface ContextProvider {
+    /**
+     * Implemented in classes that get started by the system before a context is available.
+     */
+    public interface ContextInitializer {
         void setContextAvailableCallback(ContextAvailableCallback callback);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index aafc67e..56b5d08 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -49,11 +49,13 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements SysUiServiceProvider,
-        SystemUIAppComponentFactory.ContextProvider {
+        SystemUIAppComponentFactory.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
+    private ContextComponentHelper mComponentHelper;
+
     /**
      * Hold a reference on the stuff we start.
      */
@@ -63,12 +65,24 @@
     private final Map<Class<?>, Object> mComponents = new HashMap<>();
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
 
+    public SystemUIApplication() {
+        super();
+        Log.v(TAG, "SystemUIApplication constructed.");
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
+        Log.v(TAG, "SystemUIApplication created.");
         // This line is used to setup Dagger's dependency injection and should be kept at the
         // top of this method.
+        TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
+                Trace.TRACE_TAG_APP);
+        log.traceBegin("DependencyInjection");
         mContextAvailableCallback.onContextAvailable(this);
+        mComponentHelper = SystemUIFactory
+                .getInstance().getRootComponent().getContextComponentHelper();
+        log.traceEnd();
 
         // Set the application theme that is inherited by all services. Note that setting the
         // application theme in the manifest does only work for activities. Keep this in sync with
@@ -138,7 +152,7 @@
 
     /**
      * Ensures that all the Secondary user SystemUI services are running. If they are already
-     * running, this is a no-op. This is needed to conditinally start all the services, as we only
+     * running, this is a no-op. This is needed to conditionally start all the services, as we only
      * need to have it in the main process.
      * <p>This method must only be called from the main thread.</p>
      */
@@ -159,7 +173,9 @@
             // see ActivityManagerService.finishBooting()
             if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
                 mBootCompleted = true;
-                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
+                if (DEBUG) {
+                    Log.v(TAG, "BOOT_COMPLETED was already sent");
+                }
             }
         }
 
@@ -174,15 +190,13 @@
             if (DEBUG) Log.d(TAG, "loading: " + clsName);
             log.traceBegin("StartServices" + clsName);
             long ti = System.currentTimeMillis();
-            Class cls;
             try {
-                cls = Class.forName(clsName);
-                Object o = cls.newInstance();
-                if (o instanceof SystemUI.Injector) {
-                    o = ((SystemUI.Injector) o).apply(this);
+                SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
+                if (obj == null) {
+                    obj = (SystemUI) Class.forName(clsName).newInstance();
                 }
-                mServices[i] = (SystemUI) o;
-            } catch(ClassNotFoundException ex){
+                mServices[i] = obj;
+            } catch (ClassNotFoundException ex) {
                 throw new RuntimeException(ex);
             } catch (IllegalAccessException ex) {
                 throw new RuntimeException(ex);
@@ -199,7 +213,7 @@
             // Warn if initialization of component takes too long
             ti = System.currentTimeMillis() - ti;
             if (ti > 1000) {
-                Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
+                Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
             }
             if (mBootCompleted) {
                 mServices[i].onBootCompleted();
@@ -273,6 +287,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
+            Dependency.staticOnConfigurationChanged(newConfig);
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
new file mode 100644
index 0000000..4531c89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import com.android.systemui.keyguard.KeyguardViewMediator;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * SystemUI objects that are injectable should go here.
+ */
+@Module
+public abstract class SystemUIBinder {
+    /** Inject into KeyguardViewMediator. */
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardViewMediator.class)
+    public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 39617ec..8e69318 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -98,10 +98,14 @@
     public SystemUIFactory() {}
 
     private void init(Context context) {
-        initWithRootComponent(buildSystemUIRootComponent(context));
+        mRootComponent = buildSystemUIRootComponent(context);
+
         // Every other part of our codebase currently relies on Dependency, so we
         // really need to ensure the Dependency gets initialized early on.
-        Dependency.initDependencies(context);
+
+        Dependency dependency = new Dependency();
+        mRootComponent.createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 
     protected void initWithRootComponent(@NonNull SystemUIRootComponent rootComponent) {
@@ -129,23 +133,24 @@
     }
 
     public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
-            LockPatternUtils lockPatternUtils,  ViewGroup container,
+            LockPatternUtils lockPatternUtils, ViewGroup container,
             DismissCallbackRegistry dismissCallbackRegistry,
             KeyguardBouncer.BouncerExpansionCallback expansionCallback,
             FalsingManager falsingManager, KeyguardBypassController bypassController) {
         return new KeyguardBouncer(context, callback, lockPatternUtils, container,
                 dismissCallbackRegistry, falsingManager,
                 expansionCallback, UnlockMethodCache.getInstance(context),
-                KeyguardUpdateMonitor.getInstance(context), bypassController,
+                Dependency.get(KeyguardUpdateMonitor.class), bypassController,
                 new Handler(Looper.getMainLooper()));
     }
 
     public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+            ScrimView scrimForBubble,
             LockscreenWallpaper lockscreenWallpaper,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
             AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
-        return new ScrimController(scrimBehind, scrimInFront, scrimStateListener,
+        return new ScrimController(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
                 scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
index 5d103eb..b0316e22 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIModule.java
@@ -20,10 +20,11 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 
+import com.android.systemui.assist.AssistModule;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import javax.inject.Singleton;
 
@@ -34,7 +35,7 @@
  * A dagger module for injecting components of System UI that are not overridden by the System UI
  * implementation.
  */
-@Module
+@Module(includes = {AssistModule.class, ComponentBinder.class})
 public abstract class SystemUIModule {
 
     @Singleton
@@ -46,7 +47,7 @@
         if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
             return null;
         }
-        return new KeyguardLiftController(context, statusBarStateController, asyncSensorManager);
+        return new KeyguardLiftController(statusBarStateController, asyncSensorManager);
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
index ad04f18..c70b2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIRootComponent.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui;
 
-import android.content.ContentProvider;
-
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 
+import android.content.ContentProvider;
+
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.util.InjectionInflationController;
@@ -37,11 +37,17 @@
 @Component(modules = {
         DependencyProvider.class,
         DependencyBinder.class,
-        ServiceBinder.class,
         SystemUIFactory.ContextHolder.class,
         SystemUIModule.class,
         SystemUIDefaultModule.class})
 public interface SystemUIRootComponent {
+
+    /**
+     * Creates a GarbageMonitor.
+     */
+    @Singleton
+    ContextComponentHelper getContextComponentHelper();
+
     /**
      * Main dependency providing module.
      */
@@ -66,7 +72,7 @@
     InjectionInflationController.ViewCreator createViewCreator();
 
     /**
-     * Creatse a GarbageMonitor.
+     * Creates a GarbageMonitor.
      */
     @Singleton
     GarbageMonitor createGarbageMonitor();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
index c8a2e17..2d2d91d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUISecondaryUserService.java
@@ -38,25 +38,8 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        SystemUI[] services = ((SystemUIApplication) getApplication()).getServices();
-        if (args == null || args.length == 0) {
-            for (SystemUI ui: services) {
-                if (ui != null) {
-                    pw.println("dumping service: " + ui.getClass().getName());
-                    ui.dump(fd, pw, args);
-                }
-            }
-        } else {
-            String svc = args[0];
-            for (SystemUI ui: services) {
-                if (ui != null) {
-                    String name = ui.getClass().getName();
-                    if (name.endsWith(svc)) {
-                        ui.dump(fd, pw, args);
-                    }
-                }
-            }
-        }
+        SystemUIService.dumpServices(
+                ((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index dc1218d..8f1fcae 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -66,8 +66,14 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        SystemUI[] services = ((SystemUIApplication) getApplication()).getServices();
+        dumpServices(((SystemUIApplication) getApplication()).getServices(), fd, pw, args);
+    }
+
+    static void dumpServices(
+            SystemUI[] services, FileDescriptor fd, PrintWriter pw, String[] args) {
         if (args == null || args.length == 0) {
+            pw.println("dumping service: " + Dependency.class.getName());
+            Dependency.staticDump(fd, pw, args);
             for (SystemUI ui: services) {
                 pw.println("dumping service: " + ui.getClass().getName());
                 ui.dump(fd, pw, args);
@@ -78,6 +84,9 @@
             }
         } else {
             String svc = args[0].toLowerCase();
+            if (Dependency.class.getName().toLowerCase().endsWith(svc)) {
+                Dependency.staticDump(fd, pw, args);
+            }
             for (SystemUI ui: services) {
                 String name = ui.getClass().getName().toLowerCase();
                 if (name.endsWith(svc)) {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java
index 0cee030..9bdfa03 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,21 @@
 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 +52,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 +71,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<ScreenDecorations> mScreenDecorations;
     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 +85,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<ScreenDecorations> screenDecorations,
             PhenotypeHelper phenotypeHelper,
-            @Nullable BehaviorController testBehavior) {
+            Map<AssistHandleBehavior, BehaviorController> behaviorMap,
+            NavigationModeController navigationModeController,
+            DumpController dumpController) {
         mContext = context;
         mAssistUtils = assistUtils;
         mHandler = handler;
-        mScreenDecorationsSupplier = screenDecorationsSupplier;
+        mScreenDecorations = screenDecorations;
         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 +115,8 @@
                                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_BEHAVIOR_MODE, null));
                     }
                 });
-        Dependency.get(DumpController.class).registerDumpable(TAG, this);
+
+        dumpController.registerDumpable(TAG, this);
     }
 
     @Override // AssistHandleCallbacks
@@ -177,6 +165,12 @@
         mBehaviorMap.get(mCurrentBehavior).onAssistantGesturePerformed();
     }
 
+    void onAssistHandlesRequested() {
+        if (mInGesturalMode) {
+            mBehaviorMap.get(mCurrentBehavior).onAssistHandlesRequested();
+        }
+    }
+
     void setBehavior(AssistHandleBehavior behavior) {
         if (mCurrentBehavior == behavior) {
             return;
@@ -235,7 +229,7 @@
         }
 
         if (handlesUnblocked(ignoreThreshold)) {
-            ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
+            ScreenDecorations screenDecorations = mScreenDecorations.get();
             if (screenDecorations == null) {
                 Log.w(TAG, "Couldn't show handles, ScreenDecorations unavailable");
             } else {
@@ -250,7 +244,7 @@
             return;
         }
 
-        ScreenDecorations screenDecorations = mScreenDecorationsSupplier.get();
+        ScreenDecorations screenDecorations = mScreenDecorations.get();
         if (screenDecorations == null) {
             Log.w(TAG, "Couldn't hide handles, ScreenDecorations unavailable");
         } else {
@@ -316,6 +310,7 @@
         void onModeActivated(Context context, AssistHandleCallbacks callbacks);
         default void onModeDeactivated() {}
         default void onAssistantGesturePerformed() {}
+        default void onAssistHandlesRequested() {}
         default void dump(PrintWriter pw, String prefix) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
index 763315d..ccca447 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleLikeHomeBehavior.java
@@ -20,18 +20,24 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
 /**
  * Assistant Handle behavior that makes Assistant handles show/hide when the home handle is
  * shown/hidden, respectively.
  */
+@Singleton
 final class AssistHandleLikeHomeBehavior implements BehaviorController {
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
@@ -41,35 +47,70 @@
                     handleDozingChanged(isDozing);
                 }
             };
+    private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ true);
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+            };
 
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
             this::handleSystemUiStateChange;
-    private final StatusBarStateController mStatusBarStateController;
-    private final SysUiState mSysUiFlagContainer;
+
+    private final Lazy<StatusBarStateController> mStatusBarStateController;
+    private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
+    private final Lazy<SysUiState> mSysUiFlagContainer;
 
     private boolean mIsDozing;
+    private boolean mIsAwake;
     private boolean mIsHomeHandleHiding;
 
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
 
-    AssistHandleLikeHomeBehavior() {
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
-        mSysUiFlagContainer = Dependency.get(SysUiState.class);
+    @Inject
+    AssistHandleLikeHomeBehavior(
+            Lazy<StatusBarStateController> statusBarStateController,
+            Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
+            Lazy<SysUiState> sysUiFlagContainer) {
+        mStatusBarStateController = statusBarStateController;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mSysUiFlagContainer = sysUiFlagContainer;
     }
 
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         mAssistHandleCallbacks = callbacks;
-        mIsDozing = mStatusBarStateController.isDozing();
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        mSysUiFlagContainer.addCallback(mSysUiStateCallback);
+        mIsDozing = mStatusBarStateController.get().isDozing();
+        mStatusBarStateController.get().addCallback(mStatusBarStateListener);
+        mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
+                == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+        mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
+        mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
         callbackForCurrentState();
     }
 
     @Override
     public void onModeDeactivated() {
         mAssistHandleCallbacks = null;
-        mSysUiFlagContainer.removeCallback(mSysUiStateCallback);
+        mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
+        mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
+        mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
     }
 
     private static boolean isHomeHandleHiding(int sysuiStateFlags) {
@@ -85,6 +126,15 @@
         callbackForCurrentState();
     }
 
+    private void handleWakefullnessChanged(boolean isAwake) {
+        if (mIsAwake == isAwake) {
+            return;
+        }
+
+        mIsAwake = isAwake;
+        callbackForCurrentState();
+    }
+
     private void handleSystemUiStateChange(int sysuiStateFlags) {
         boolean isHomeHandleHiding = isHomeHandleHiding(sysuiStateFlags);
         if (mIsHomeHandleHiding == isHomeHandleHiding) {
@@ -100,18 +150,23 @@
             return;
         }
 
-        if (mIsHomeHandleHiding || mIsDozing) {
+        if (mIsHomeHandleHiding || !isFullyAwake()) {
             mAssistHandleCallbacks.hide();
         } else {
             mAssistHandleCallbacks.showAndStay();
         }
     }
 
+    private boolean isFullyAwake() {
+        return mIsAwake && !mIsDozing;
+    }
+
     @Override
     public void dump(PrintWriter pw, String prefix) {
-        pw.println("Current AssistHandleLikeHomeBehavior State:");
+        pw.println(prefix + "Current AssistHandleLikeHomeBehavior State:");
 
         pw.println(prefix + "   mIsDozing=" + mIsDozing);
+        pw.println(prefix + "   mIsAwake=" + mIsAwake);
         pw.println(prefix + "   mIsHomeHandleHiding=" + mIsHomeHandleHiding);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
index f4130ae..df913f9 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleOffBehavior.java
@@ -20,9 +20,17 @@
 
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /** Assistant handle behavior that hides the Assistant handles. */
+@Singleton
 final class AssistHandleOffBehavior implements BehaviorController {
 
+    @Inject
+    AssistHandleOffBehavior() {
+    }
+
     @Override
     public void onModeActivated(Context context, AssistHandleCallbacks callbacks) {
         callbacks.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
index 7a9feb7..3ff1b97 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.assist;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.assist.AssistModule.ASSIST_HANDLE_THREAD_NAME;
+import static com.android.systemui.assist.AssistModule.UPTIME_NAME;
 
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
@@ -26,14 +27,14 @@
 import android.content.IntentFilter;
 import android.content.pm.ResolveInfo;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.provider.Settings;
 
 import androidx.annotation.Nullable;
+import androidx.slice.Clock;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.Dependency;
 import com.android.systemui.assist.AssistHandleBehaviorController.BehaviorController;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.OverviewProxyService;
@@ -49,11 +50,18 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
 /**
  * Assistant handle behavior that hides the handles when the phone is dozing or in immersive mode,
  * shows the handles when on lockscreen, and shows the handles temporarily when changing tasks or
  * entering overview.
  */
+@Singleton
 final class AssistHandleReminderExpBehavior implements BehaviorController {
 
     private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed";
@@ -111,9 +119,30 @@
                     handleOverviewShown();
                 }
             };
-
     private final SysUiState.SysUiStateCallback mSysUiStateCallback =
             this::handleSystemUiStateChanged;
+    private final WakefulnessLifecycle.Observer mWakefulnessLifecycleObserver =
+            new WakefulnessLifecycle.Observer() {
+                @Override
+                public void onStartedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedWakingUp() {
+                    handleWakefullnessChanged(/* isAwake = */ true);
+                }
+
+                @Override
+                public void onStartedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+
+                @Override
+                public void onFinishedGoingToSleep() {
+                    handleWakefullnessChanged(/* isAwake = */ false);
+                }
+            };
     private final BroadcastReceiver mDefaultHomeBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -123,15 +152,19 @@
     private final IntentFilter mDefaultHomeIntentFilter;
     private final Runnable mResetConsecutiveTaskSwitches = this::resetConsecutiveTaskSwitches;
 
+    private final Clock mClock;
     private final Handler mHandler;
     private final PhenotypeHelper mPhenotypeHelper;
-    private final StatusBarStateController mStatusBarStateController;
-    private final ActivityManagerWrapper mActivityManagerWrapper;
-    private final OverviewProxyService mOverviewProxyService;
-    private final SysUiState mSysUiFlagContainer;
+    private final Lazy<StatusBarStateController> mStatusBarStateController;
+    private final Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
+    private final Lazy<OverviewProxyService> mOverviewProxyService;
+    private final Lazy<SysUiState> mSysUiFlagContainer;
+    private final Lazy<WakefulnessLifecycle> mWakefulnessLifecycle;
+    private final Lazy<PackageManagerWrapper> mPackageManagerWrapper;
 
     private boolean mOnLockscreen;
     private boolean mIsDozing;
+    private boolean mIsAwake;
     private int mRunningTaskId;
     private boolean mIsNavBarHidden;
     private boolean mIsLauncherShowing;
@@ -150,13 +183,26 @@
     @Nullable private AssistHandleCallbacks mAssistHandleCallbacks;
     @Nullable private ComponentName mDefaultHome;
 
-    AssistHandleReminderExpBehavior(Handler handler, PhenotypeHelper phenotypeHelper) {
+    @Inject
+    AssistHandleReminderExpBehavior(
+            @Named(UPTIME_NAME) Clock clock,
+            @Named(ASSIST_HANDLE_THREAD_NAME) Handler handler,
+            PhenotypeHelper phenotypeHelper,
+            Lazy<StatusBarStateController> statusBarStateController,
+            Lazy<ActivityManagerWrapper> activityManagerWrapper,
+            Lazy<OverviewProxyService> overviewProxyService,
+            Lazy<SysUiState> sysUiFlagContainer,
+            Lazy<WakefulnessLifecycle> wakefulnessLifecycle,
+            Lazy<PackageManagerWrapper> packageManagerWrapper) {
+        mClock = clock;
         mHandler = handler;
         mPhenotypeHelper = phenotypeHelper;
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
-        mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
-        mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mSysUiFlagContainer = Dependency.get(SysUiState.class);
+        mStatusBarStateController = statusBarStateController;
+        mActivityManagerWrapper = activityManagerWrapper;
+        mOverviewProxyService = overviewProxyService;
+        mSysUiFlagContainer = sysUiFlagContainer;
+        mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPackageManagerWrapper = packageManagerWrapper;
         mDefaultHomeIntentFilter = new IntentFilter();
         for (String action : DEFAULT_HOME_CHANGE_ACTIONS) {
             mDefaultHomeIntentFilter.addAction(action);
@@ -170,14 +216,18 @@
         mConsecutiveTaskSwitches = 0;
         mDefaultHome = getCurrentDefaultHome();
         context.registerReceiver(mDefaultHomeBroadcastReceiver, mDefaultHomeIntentFilter);
-        mOnLockscreen = onLockscreen(mStatusBarStateController.getState());
-        mIsDozing = mStatusBarStateController.isDozing();
-        mStatusBarStateController.addCallback(mStatusBarStateListener);
-        ActivityManager.RunningTaskInfo runningTaskInfo = mActivityManagerWrapper.getRunningTask();
+        mOnLockscreen = onLockscreen(mStatusBarStateController.get().getState());
+        mIsDozing = mStatusBarStateController.get().isDozing();
+        mStatusBarStateController.get().addCallback(mStatusBarStateListener);
+        ActivityManager.RunningTaskInfo runningTaskInfo =
+                mActivityManagerWrapper.get().getRunningTask();
         mRunningTaskId = runningTaskInfo == null ? 0 : runningTaskInfo.taskId;
-        mActivityManagerWrapper.registerTaskStackListener(mTaskStackChangeListener);
-        mOverviewProxyService.addCallback(mOverviewProxyListener);
-        mSysUiFlagContainer.addCallback(mSysUiStateCallback);
+        mActivityManagerWrapper.get().registerTaskStackListener(mTaskStackChangeListener);
+        mOverviewProxyService.get().addCallback(mOverviewProxyListener);
+        mSysUiFlagContainer.get().addCallback(mSysUiStateCallback);
+        mIsAwake = mWakefulnessLifecycle.get().getWakefulness()
+                == WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+        mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver);
 
         mLearningTimeElapsed = Settings.Secure.getLong(
                 context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0);
@@ -185,7 +235,7 @@
                 context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0);
         mLearnedHintLastShownEpochDay = Settings.Secure.getLong(
                 context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0);
-        mLastLearningTimestamp = SystemClock.uptimeMillis();
+        mLastLearningTimestamp = mClock.currentTimeMillis();
 
         callbackForCurrentState(/* justUnlocked = */ false);
     }
@@ -200,10 +250,11 @@
             Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0);
             mContext = null;
         }
-        mStatusBarStateController.removeCallback(mStatusBarStateListener);
-        mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackChangeListener);
-        mOverviewProxyService.removeCallback(mOverviewProxyListener);
-        mSysUiFlagContainer.removeCallback(mSysUiStateCallback);
+        mStatusBarStateController.get().removeCallback(mStatusBarStateListener);
+        mActivityManagerWrapper.get().unregisterTaskStackListener(mTaskStackChangeListener);
+        mOverviewProxyService.get().removeCallback(mOverviewProxyListener);
+        mSysUiFlagContainer.get().removeCallback(mSysUiStateCallback);
+        mWakefulnessLifecycle.get().removeObserver(mWakefulnessLifecycleObserver);
     }
 
     @Override
@@ -216,15 +267,20 @@
                 mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount);
     }
 
-    private static boolean isNavBarHidden(int sysuiStateFlags) {
-        return (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+    @Override
+    public void onAssistHandlesRequested() {
+        if (mAssistHandleCallbacks != null
+                && isFullyAwake()
+                && !mIsNavBarHidden
+                && !mOnLockscreen) {
+            mAssistHandleCallbacks.showAndGo();
+        }
     }
 
     @Nullable
-    private static ComponentName getCurrentDefaultHome() {
+    private ComponentName getCurrentDefaultHome() {
         List<ResolveInfo> homeActivities = new ArrayList<>();
-        ComponentName defaultHome =
-                PackageManagerWrapper.getInstance().getHomeActivities(homeActivities);
+        ComponentName defaultHome = mPackageManagerWrapper.get().getHomeActivities(homeActivities);
         if (defaultHome != null) {
             return defaultHome;
         }
@@ -263,6 +319,16 @@
         callbackForCurrentState(/* justUnlocked = */ false);
     }
 
+    private void handleWakefullnessChanged(boolean isAwake) {
+        if (mIsAwake == isAwake) {
+            return;
+        }
+
+        resetConsecutiveTaskSwitches();
+        mIsAwake = isAwake;
+        callbackForCurrentState(/* justUnlocked = */ false);
+    }
+
     private void handleTaskStackTopChanged(int taskId, @Nullable ComponentName taskComponentName) {
         if (mRunningTaskId == taskId || taskComponentName == null) {
             return;
@@ -280,7 +346,8 @@
     }
 
     private void handleSystemUiStateChanged(int sysuiStateFlags) {
-        boolean isNavBarHidden = isNavBarHidden(sysuiStateFlags);
+        boolean isNavBarHidden =
+                (sysuiStateFlags & QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
         if (mIsNavBarHidden == isNavBarHidden) {
             return;
         }
@@ -315,7 +382,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
+        if (!isFullyAwake() || mIsNavBarHidden || mOnLockscreen || !getShowWhenTaught()) {
             mAssistHandleCallbacks.hide();
         } else if (justUnlocked) {
             long currentEpochDay = LocalDate.now().toEpochDay();
@@ -337,7 +404,7 @@
             return;
         }
 
-        if (mIsDozing || mIsNavBarHidden || isSuppressed()) {
+        if (!isFullyAwake() || mIsNavBarHidden || isSuppressed()) {
             mAssistHandleCallbacks.hide();
         } else if (mOnLockscreen) {
             mAssistHandleCallbacks.showAndStay();
@@ -367,15 +434,15 @@
             return;
         }
 
-        long currentTimestamp = SystemClock.uptimeMillis();
+        long currentTimestamp = mClock.currentTimeMillis();
         mLearningTimeElapsed += currentTimestamp - mLastLearningTimestamp;
         mLastLearningTimestamp = currentTimestamp;
-        // TODO(b/140034473)
-        whitelistIpcs(() -> Settings.Secure.putLong(
-                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
 
         mIsLearned =
                 mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs();
+
+        mHandler.post(() -> Settings.Secure.putLong(
+                mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed));
     }
 
     private void resetConsecutiveTaskSwitches() {
@@ -388,6 +455,10 @@
         mHandler.postDelayed(mResetConsecutiveTaskSwitches, getShowAndGoDelayResetTimeoutMs());
     }
 
+    private boolean isFullyAwake() {
+        return mIsAwake && !mIsDozing;
+    }
+
     private long getLearningTimeMs() {
         return mPhenotypeHelper.getLong(
                 SystemUiDeviceConfigFlags.ASSIST_HANDLES_LEARN_TIME_MS,
@@ -447,6 +518,7 @@
         pw.println(prefix + "Current AssistHandleReminderExpBehavior State:");
         pw.println(prefix + "   mOnLockscreen=" + mOnLockscreen);
         pw.println(prefix + "   mIsDozing=" + mIsDozing);
+        pw.println(prefix + "   mIsAwake=" + mIsAwake);
         pw.println(prefix + "   mRunningTaskId=" + mRunningTaskId);
         pw.println(prefix + "   mDefaultHome=" + mDefaultHome);
         pw.println(prefix + "   mIsNavBarHidden=" + mIsNavBarHidden);
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index c4feac1..1f950f6 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -103,6 +103,8 @@
     private static final String INVOCATION_TIME_MS_KEY = "invocation_time_ms";
     private static final String INVOCATION_PHONE_STATE_KEY = "invocation_phone_state";
     public static final String INVOCATION_TYPE_KEY = "invocation_type";
+    protected static final String ACTION_KEY = "action";
+    protected static final String SHOW_ASSIST_HANDLES_ACTION = "show_assist_handles";
 
     public static final int INVOCATION_TYPE_GESTURE = 1;
     public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2;
@@ -154,15 +156,18 @@
     };
 
     @Inject
-    public AssistManager(DeviceProvisionedController controller, Context context) {
+    public AssistManager(
+            DeviceProvisionedController controller,
+            Context context,
+            AssistUtils assistUtils,
+            AssistHandleBehaviorController handleController) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        mAssistUtils = new AssistUtils(context);
+        mAssistUtils = assistUtils;
         mAssistDisclosure = new AssistDisclosure(context, new Handler());
         mPhoneStateMonitor = new PhoneStateMonitor(context);
-        mHandleController =
-                new AssistHandleBehaviorController(context, mAssistUtils, new Handler());
+        mHandleController = handleController;
 
         registerVoiceInteractionSessionListener();
         mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION
@@ -211,6 +216,9 @@
                         if (VERBOSE) {
                             Log.v(TAG, "UI hints received");
                         }
+                        if (SHOW_ASSIST_HANDLES_ACTION.equals(hints.getString(ACTION_KEY))) {
+                            requestAssistHandles();
+                        }
                     }
                 });
     }
@@ -284,6 +292,10 @@
         mUiController.onGestureCompletion(velocity);
     }
 
+    protected void requestAssistHandles() {
+        mHandleController.onAssistHandlesRequested();
+    }
+
     public void hideAssist() {
         mAssistUtils.hideCurrentSession();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
new file mode 100644
index 0000000..2a82d21
--- /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.ScreenDecorations;
+import com.android.systemui.SysUiServiceProvider;
+
+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 ScreenDecorations provideScreenDecorations(Context context) {
+        return SysUiServiceProvider.getComponent(context, ScreenDecorations.class);
+    }
+
+    @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..b346a6e 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;
@@ -40,6 +42,8 @@
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.assist.AssistManager;
 
+import java.util.Locale;
+
 /**
  * Default UiController implementation. Shows white edge lights along the bottom of the phone,
  * expanding from the corners to meet in the center.
@@ -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));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
new file mode 100644
index 0000000..1d47fc5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.content.Context;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+public class AuthBiometricFaceView extends AuthBiometricView {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
+
+    // Delay before dismissing after being authenticated/confirmed.
+    private static final int HIDE_DELAY_MS = 500;
+
+    public static class IconController extends Animatable2.AnimationCallback {
+        Context mContext;
+        ImageView mIconView;
+        TextView mTextView;
+        Handler mHandler;
+        boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
+        @BiometricState int mState;
+
+        IconController(Context context, ImageView iconView, TextView textView) {
+            mContext = context;
+            mIconView = iconView;
+            mTextView = textView;
+            mHandler = new Handler(Looper.getMainLooper());
+            showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+        }
+
+        void animateOnce(int iconRes) {
+            animateIcon(iconRes, false);
+        }
+
+        public void showStaticDrawable(int iconRes) {
+            mIconView.setImageDrawable(mContext.getDrawable(iconRes));
+        }
+
+        void animateIcon(int iconRes, boolean repeat) {
+            final AnimatedVectorDrawable icon =
+                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
+            mIconView.setImageDrawable(icon);
+            icon.forceAnimationOnUI();
+            if (repeat) {
+                icon.registerAnimationCallback(this);
+            }
+            icon.start();
+        }
+
+        void startPulsing() {
+            mLastPulseLightToDark = false;
+            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
+        }
+
+        void pulseInNextDirection() {
+            int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
+                    : R.drawable.face_dialog_pulse_light_to_dark;
+            animateIcon(iconRes, true /* repeat */);
+            mLastPulseLightToDark = !mLastPulseLightToDark;
+        }
+
+        @Override
+        public void onAnimationEnd(Drawable drawable) {
+            super.onAnimationEnd(drawable);
+            if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
+                pulseInNextDirection();
+            }
+        }
+
+        public void updateState(int lastState, int newState) {
+            final boolean lastStateIsErrorIcon =
+                    lastState == STATE_ERROR || lastState == STATE_HELP;
+
+            if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+                showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticating));
+            } else if (newState == STATE_AUTHENTICATING) {
+                startPulsing();
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticating));
+            } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_confirmed));
+            } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+                animateOnce(R.drawable.face_dialog_error_to_idle);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_idle));
+            } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
+                animateOnce(R.drawable.face_dialog_dark_to_error);
+            } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+                animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_PENDING_CONFIRMATION) {
+                animateOnce(R.drawable.face_dialog_wink_from_dark);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_authenticated));
+            } else if (newState == STATE_IDLE) {
+                showStaticDrawable(R.drawable.face_dialog_idle_static);
+                mIconView.setContentDescription(mContext.getString(
+                        R.string.biometric_dialog_face_icon_description_idle));
+            } else {
+                Log.w(TAG, "Unhandled state: " + newState);
+            }
+            mState = newState;
+        }
+    }
+
+    @VisibleForTesting IconController mIconController;
+
+    public AuthBiometricFaceView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricFaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int getDelayAfterAuthenticatedDurationMs() {
+        return HIDE_DELAY_MS;
+    }
+
+    @Override
+    protected int getStateForAfterError() {
+        return STATE_IDLE;
+    }
+
+    @Override
+    protected void handleResetAfterError() {
+        resetErrorView(mContext, mIndicatorView);
+    }
+
+    @Override
+    protected void handleResetAfterHelp() {
+        resetErrorView(mContext, mIndicatorView);
+    }
+
+    @Override
+    protected boolean supportsSmallDialog() {
+        return true;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconController = new IconController(mContext, mIconView, mIndicatorView);
+    }
+
+    @Override
+    public void updateState(@BiometricState int newState) {
+        mIconController.updateState(mState, newState);
+
+        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+                (newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
+            resetErrorView(mContext, mIndicatorView);
+        }
+
+        // Do this last since the state variable gets updated.
+        super.updateState(newState);
+    }
+
+    @Override
+    public void onAuthenticationFailed(String failureReason) {
+        if (mSize == AuthDialog.SIZE_MEDIUM) {
+            mTryAgainButton.setVisibility(View.VISIBLE);
+            mPositiveButton.setVisibility(View.GONE);
+        }
+
+        // Do this last since wa want to know if the button is being animated (in the case of
+        // small -> medium dialog)
+        super.onAuthenticationFailed(failureReason);
+    }
+
+    static void resetErrorView(Context context, TextView textView) {
+        textView.setTextColor(context.getResources().getColor(
+                R.color.biometric_dialog_gray, context.getTheme()));
+        textView.setVisibility(View.INVISIBLE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
new file mode 100644
index 0000000..176e9e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+
+import android.content.Context;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.systemui.R;
+
+public class AuthBiometricFingerprintView extends AuthBiometricView {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView";
+
+    public AuthBiometricFingerprintView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricFingerprintView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected int getDelayAfterAuthenticatedDurationMs() {
+        return 0;
+    }
+
+    @Override
+    protected int getStateForAfterError() {
+        return STATE_AUTHENTICATING;
+    }
+
+    @Override
+    protected void handleResetAfterError() {
+        showTouchSensorString();
+    }
+
+    @Override
+    protected void handleResetAfterHelp() {
+        showTouchSensorString();
+    }
+
+    @Override
+    protected boolean supportsSmallDialog() {
+        return false;
+    }
+
+    @Override
+    public void updateState(@BiometricState int newState) {
+        updateIcon(mState, newState);
+
+        // Do this last since the state variable gets updated.
+        super.updateState(newState);
+    }
+
+    @Override
+    void onAttachedToWindowInternal() {
+        super.onAttachedToWindowInternal();
+        showTouchSensorString();
+    }
+
+    private void showTouchSensorString() {
+        mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor);
+        mIndicatorView.setTextColor(R.color.biometric_dialog_gray);
+    }
+
+    private void updateIcon(int lastState, int newState) {
+        final Drawable icon = getAnimationForTransition(lastState, newState);
+        if (icon == null) {
+            Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
+            return;
+        }
+
+        final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
+                ? (AnimatedVectorDrawable) icon
+                : null;
+
+        mIconView.setImageDrawable(icon);
+
+        if (animation != null && shouldAnimateForTransition(lastState, newState)) {
+            animation.forceAnimationOnUI();
+            animation.start();
+        }
+    }
+
+    private boolean shouldAnimateForTransition(int oldState, int newState) {
+        switch (newState) {
+            case STATE_HELP:
+            case STATE_ERROR:
+                return true;
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    return true;
+                } else {
+                    return false;
+                }
+            case STATE_AUTHENTICATED:
+                return false;
+            default:
+                return false;
+        }
+    }
+
+    private Drawable getAnimationForTransition(int oldState, int newState) {
+        int iconRes;
+
+        switch (newState) {
+            case STATE_HELP:
+            case STATE_ERROR:
+                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                break;
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                if (oldState == STATE_ERROR || oldState == STATE_HELP) {
+                    iconRes = R.drawable.fingerprint_dialog_error_to_fp;
+                } else {
+                    iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                }
+                break;
+            case STATE_AUTHENTICATED:
+                iconRes = R.drawable.fingerprint_dialog_fp_to_error;
+                break;
+            default:
+                return null;
+        }
+
+        return mContext.getDrawable(iconRes);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
new file mode 100644
index 0000000..73bbce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ */
+public abstract class AuthBiometricView extends LinearLayout {
+
+    private static final String TAG = "BiometricPrompt/AuthBiometricView";
+
+    /**
+     * Authentication hardware idle.
+     */
+    protected static final int STATE_IDLE = 0;
+    /**
+     * UI animating in, authentication hardware active.
+     */
+    protected static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
+    /**
+     * UI animated in, authentication hardware active.
+     */
+    protected static final int STATE_AUTHENTICATING = 2;
+    /**
+     * UI animated in, authentication hardware active.
+     */
+    protected static final int STATE_HELP = 3;
+    /**
+     * Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle.
+     */
+    protected static final int STATE_ERROR = 4;
+    /**
+     * Authenticated, waiting for user confirmation. Authentication hardware idle.
+     */
+    protected static final int STATE_PENDING_CONFIRMATION = 5;
+    /**
+     * Authenticated, dialog animating away soon.
+     */
+    protected static final int STATE_AUTHENTICATED = 6;
+    
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
+            STATE_ERROR, STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED})
+    @interface BiometricState {}
+
+    /**
+     * Callback to the parent when a user action has occurred.
+     */
+    interface Callback {
+        int ACTION_AUTHENTICATED = 1;
+        int ACTION_USER_CANCELED = 2;
+        int ACTION_BUTTON_NEGATIVE = 3;
+        int ACTION_BUTTON_TRY_AGAIN = 4;
+        int ACTION_ERROR = 5;
+
+        /**
+         * When an action has occurred. The caller will only invoke this when the callback should
+         * be propagated. e.g. the caller will handle any necessary delay.
+         * @param action
+         */
+        void onAction(int action);
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        AuthBiometricView mBiometricView;
+
+        public Button getNegativeButton() {
+            return mBiometricView.findViewById(R.id.button_negative);
+        }
+
+        public Button getPositiveButton() {
+            return mBiometricView.findViewById(R.id.button_positive);
+        }
+
+        public Button getTryAgainButton() {
+            return mBiometricView.findViewById(R.id.button_try_again);
+        }
+
+        public TextView getTitleView() {
+            return mBiometricView.findViewById(R.id.title);
+        }
+
+        public TextView getSubtitleView() {
+            return mBiometricView.findViewById(R.id.subtitle);
+        }
+
+        public TextView getDescriptionView() {
+            return mBiometricView.findViewById(R.id.description);
+        }
+
+        public TextView getIndicatorView() {
+            return mBiometricView.findViewById(R.id.indicator);
+        }
+
+        public ImageView getIconView() {
+            return mBiometricView.findViewById(R.id.biometric_icon);
+        }
+
+        public int getDelayAfterError() {
+            return BiometricPrompt.HIDE_DIALOG_DELAY;
+        }
+    }
+
+    private final Injector mInjector;
+    private final Handler mHandler;
+    private final AccessibilityManager mAccessibilityManager;
+    private final int mTextColorError;
+    private final int mTextColorHint;
+
+    private AuthPanelController mPanelController;
+    private Bundle mBundle;
+    private boolean mRequireConfirmation;
+    @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
+
+    private TextView mTitleView;
+    private TextView mSubtitleView;
+    private TextView mDescriptionView;
+    protected ImageView mIconView;
+    @VisibleForTesting protected TextView mIndicatorView;
+    @VisibleForTesting Button mNegativeButton;
+    @VisibleForTesting Button mPositiveButton;
+    @VisibleForTesting Button mTryAgainButton;
+
+    // Measurements when biometric view is showing text, buttons, etc.
+    private int mMediumHeight;
+    private int mMediumWidth;
+
+    private Callback mCallback;
+    protected @BiometricState int mState;
+
+    private float mIconOriginalY;
+
+    protected boolean mDialogSizeAnimating;
+    protected Bundle mSavedState;
+
+    /**
+     * Delay after authentication is confirmed, before the dialog should be animated away.
+     */
+    protected abstract int getDelayAfterAuthenticatedDurationMs();
+    /**
+     * State that the dialog/icon should be in after showing a help message.
+     */
+    protected abstract int getStateForAfterError();
+    /**
+     * Invoked when the error message is being cleared.
+     */
+    protected abstract void handleResetAfterError();
+    /**
+     * Invoked when the help message is being cleared.
+     */
+    protected abstract void handleResetAfterHelp();
+
+    /**
+     * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}
+     */
+    protected abstract boolean supportsSmallDialog();
+
+    private final Runnable mResetErrorRunnable;
+
+    private final Runnable mResetHelpRunnable;
+
+    private final OnClickListener mBackgroundClickListener = (view) -> {
+        if (mState == STATE_AUTHENTICATED) {
+            Log.w(TAG, "Ignoring background click after authenticated");
+            return;
+        } else if (mSize == AuthDialog.SIZE_SMALL) {
+            Log.w(TAG, "Ignoring background click during small dialog");
+            return;
+        }
+        mCallback.onAction(Callback.ACTION_USER_CANCELED);
+    };
+
+    public AuthBiometricView(Context context) {
+        this(context, null);
+    }
+
+    public AuthBiometricView(Context context, AttributeSet attrs) {
+        this(context, attrs, new Injector());
+    }
+
+    @VisibleForTesting
+    AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
+        super(context, attrs);
+        mHandler = new Handler(Looper.getMainLooper());
+        mTextColorError = getResources().getColor(
+                R.color.biometric_dialog_error, context.getTheme());
+        mTextColorHint = getResources().getColor(
+                R.color.biometric_dialog_gray, context.getTheme());
+
+        mInjector = injector;
+        mInjector.mBiometricView = this;
+
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+
+        mResetErrorRunnable = () -> {
+            updateState(getStateForAfterError());
+            handleResetAfterError();
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        };
+
+        mResetHelpRunnable = () -> {
+            updateState(STATE_AUTHENTICATING);
+            handleResetAfterHelp();
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        };
+    }
+
+    public void setPanelController(AuthPanelController panelController) {
+        mPanelController = panelController;
+    }
+
+    public void setBiometricPromptBundle(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    public void setBackgroundView(View backgroundView) {
+        backgroundView.setOnClickListener(mBackgroundClickListener);
+    }
+
+    public void setRequireConfirmation(boolean requireConfirmation) {
+        mRequireConfirmation = requireConfirmation;
+    }
+
+    @VisibleForTesting
+    void updateSize(@AuthDialog.DialogSize int newSize) {
+        Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
+        if (newSize == AuthDialog.SIZE_SMALL) {
+            mTitleView.setVisibility(View.GONE);
+            mSubtitleView.setVisibility(View.GONE);
+            mDescriptionView.setVisibility(View.GONE);
+            mIndicatorView.setVisibility(View.GONE);
+            mNegativeButton.setVisibility(View.GONE);
+
+            final float iconPadding = getResources()
+                    .getDimension(R.dimen.biometric_dialog_icon_padding);
+            mIconView.setY(getHeight() - mIconView.getHeight() - iconPadding);
+
+            final int newHeight = mIconView.getHeight() + 2 * (int) iconPadding;
+            mPanelController.updateForContentDimensions(mMediumWidth, newHeight,
+                    false /* animate */);
+
+            mSize = newSize;
+        } else if (mSize == AuthDialog.SIZE_SMALL && newSize == AuthDialog.SIZE_MEDIUM) {
+            if (mDialogSizeAnimating) {
+                return;
+            }
+            mDialogSizeAnimating = true;
+
+            // Animate the icon back to original position
+            final ValueAnimator iconAnimator =
+                    ValueAnimator.ofFloat(mIconView.getY(), mIconOriginalY);
+            iconAnimator.addUpdateListener((animation) -> {
+                mIconView.setY((float) animation.getAnimatedValue());
+            });
+
+            // Animate the text
+            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+            opacityAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            opacityAnimator.addUpdateListener((animation) -> {
+                final float opacity = (float) animation.getAnimatedValue();
+
+                mTitleView.setAlpha(opacity);
+                mIndicatorView.setAlpha(opacity);
+                mNegativeButton.setAlpha(opacity);
+                mTryAgainButton.setAlpha(opacity);
+
+                if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+                    mSubtitleView.setAlpha(opacity);
+                }
+                if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+                    mDescriptionView.setAlpha(opacity);
+                }
+            });
+
+            // Choreograph together
+            final AnimatorSet as = new AnimatorSet();
+            as.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            as.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    super.onAnimationStart(animation);
+                    mTitleView.setVisibility(View.VISIBLE);
+                    mIndicatorView.setVisibility(View.VISIBLE);
+                    mNegativeButton.setVisibility(View.VISIBLE);
+                    mTryAgainButton.setVisibility(View.VISIBLE);
+
+                    if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+                        mSubtitleView.setVisibility(View.VISIBLE);
+                    }
+                    if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+                        mDescriptionView.setVisibility(View.VISIBLE);
+                    }
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    super.onAnimationEnd(animation);
+                    mSize = newSize;
+                    mDialogSizeAnimating = false;
+                    Utils.notifyAccessibilityContentChanged(mAccessibilityManager,
+                            AuthBiometricView.this);
+                }
+            });
+
+            as.play(iconAnimator).with(opacityAnimator);
+            as.start();
+            // Animate the panel
+            mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+                    true /* animate */);
+        } else if (newSize == AuthDialog.SIZE_MEDIUM) {
+            mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+                    false /* animate */);
+            mSize = newSize;
+        } else {
+            Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
+        }
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    public void updateState(@BiometricState int newState) {
+        Log.v(TAG, "newState: " + newState);
+
+        switch (newState) {
+            case STATE_AUTHENTICATING_ANIMATING_IN:
+            case STATE_AUTHENTICATING:
+                removePendingAnimations();
+                if (mRequireConfirmation) {
+                    mPositiveButton.setEnabled(false);
+                    mPositiveButton.setVisibility(View.VISIBLE);
+                }
+                break;
+
+            case STATE_AUTHENTICATED:
+                if (mSize != AuthDialog.SIZE_SMALL) {
+                    mPositiveButton.setVisibility(View.GONE);
+                    mNegativeButton.setVisibility(View.GONE);
+                    mIndicatorView.setVisibility(View.INVISIBLE);
+                }
+                announceForAccessibility(getResources()
+                        .getString(R.string.biometric_dialog_authenticated));
+                mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
+                        getDelayAfterAuthenticatedDurationMs());
+                break;
+
+            case STATE_PENDING_CONFIRMATION:
+                removePendingAnimations();
+                mNegativeButton.setText(R.string.cancel);
+                mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
+                mPositiveButton.setEnabled(true);
+                mPositiveButton.setVisibility(View.VISIBLE);
+                mIndicatorView.setTextColor(mTextColorHint);
+                mIndicatorView.setText(R.string.biometric_dialog_tap_confirm);
+                mIndicatorView.setVisibility(View.VISIBLE);
+                break;
+
+            case STATE_ERROR:
+                if (mSize == AuthDialog.SIZE_SMALL) {
+                    updateSize(AuthDialog.SIZE_MEDIUM);
+                }
+                break;
+
+            default:
+                Log.w(TAG, "Unhandled state: " + newState);
+                break;
+        }
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        mState = newState;
+    }
+
+    public void onDialogAnimatedIn() {
+        updateState(STATE_AUTHENTICATING);
+    }
+
+    public void onAuthenticationSucceeded() {
+        removePendingAnimations();
+        if (mRequireConfirmation) {
+            updateState(STATE_PENDING_CONFIRMATION);
+        } else {
+            updateState(STATE_AUTHENTICATED);
+        }
+    }
+
+    public void onAuthenticationFailed(String failureReason) {
+        showTemporaryMessage(failureReason, mResetErrorRunnable);
+        updateState(STATE_ERROR);
+    }
+
+    public void onError(String error) {
+        showTemporaryMessage(error, mResetErrorRunnable);
+        updateState(STATE_ERROR);
+
+        mHandler.postDelayed(() -> {
+            mCallback.onAction(Callback.ACTION_ERROR);
+        }, mInjector.getDelayAfterError());
+    }
+
+    public void onHelp(String help) {
+        if (mSize != AuthDialog.SIZE_MEDIUM) {
+            Log.w(TAG, "Help received in size: " + mSize);
+            return;
+        }
+        showTemporaryMessage(help, mResetHelpRunnable);
+        updateState(STATE_HELP);
+    }
+
+    public void onSaveState(@NonNull Bundle outState) {
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
+                mTryAgainButton.getVisibility());
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
+        outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
+                mIndicatorView.getText().toString());
+        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
+                mHandler.hasCallbacks(mResetErrorRunnable));
+        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
+                mHandler.hasCallbacks(mResetHelpRunnable));
+        outState.putInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE, mSize);
+    }
+
+    /**
+     * Invoked after inflation but before being attached to window.
+     * @param savedState
+     */
+    public void restoreState(@Nullable Bundle savedState) {
+        mSavedState = savedState;
+    }
+
+    private void setTextOrHide(TextView view, String string) {
+        if (TextUtils.isEmpty(string)) {
+            view.setVisibility(View.GONE);
+        } else {
+            view.setText(string);
+        }
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    private void setText(TextView view, String string) {
+        view.setText(string);
+    }
+
+    // Remove all pending icon and text animations
+    private void removePendingAnimations() {
+        mHandler.removeCallbacks(mResetHelpRunnable);
+        mHandler.removeCallbacks(mResetErrorRunnable);
+    }
+
+    private void showTemporaryMessage(String message, Runnable resetMessageRunnable) {
+        removePendingAnimations();
+        mIndicatorView.setText(message);
+        mIndicatorView.setTextColor(mTextColorError);
+        mIndicatorView.setVisibility(View.VISIBLE);
+        mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
+
+        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        onFinishInflateInternal();
+    }
+
+    /**
+     * After inflation, but before things like restoreState, onAttachedToWindow, etc.
+     */
+    @VisibleForTesting
+    void onFinishInflateInternal() {
+        mTitleView = mInjector.getTitleView();
+        mSubtitleView = mInjector.getSubtitleView();
+        mDescriptionView = mInjector.getDescriptionView();
+        mIconView = mInjector.getIconView();
+        mIndicatorView = mInjector.getIndicatorView();
+        mNegativeButton = mInjector.getNegativeButton();
+        mPositiveButton = mInjector.getPositiveButton();
+        mTryAgainButton = mInjector.getTryAgainButton();
+
+        mNegativeButton.setOnClickListener((view) -> {
+            if (mState == STATE_PENDING_CONFIRMATION) {
+                mCallback.onAction(Callback.ACTION_USER_CANCELED);
+            } else {
+                mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
+            }
+        });
+
+        mPositiveButton.setOnClickListener((view) -> {
+            updateState(STATE_AUTHENTICATED);
+        });
+
+        mTryAgainButton.setOnClickListener((view) -> {
+            updateState(STATE_AUTHENTICATING);
+            mCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN);
+            mTryAgainButton.setVisibility(View.GONE);
+            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
+        });
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        onAttachedToWindowInternal();
+    }
+
+    /**
+     * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is
+     * invoked.
+     */
+    @VisibleForTesting
+    void onAttachedToWindowInternal() {
+        setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE));
+        setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
+
+        setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE));
+        setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+
+        if (mSavedState == null) {
+            updateState(STATE_AUTHENTICATING_ANIMATING_IN);
+        } else {
+            // Restore as much state as possible first
+            updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+
+            // Restore positive button state
+            mTryAgainButton.setVisibility(
+                    mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
+        // the new dialog is restored.
+        mHandler.removeCallbacksAndMessages(null /* all */);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        final int height = MeasureSpec.getSize(heightMeasureSpec);
+        final int newWidth = Math.min(width, height);
+
+        int totalHeight = 0;
+        final int numChildren = getChildCount();
+        for (int i = 0; i < numChildren; i++) {
+            final View child = getChildAt(i);
+
+            if (child.getId() == R.id.biometric_icon) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+            } else if (child.getId() == R.id.button_bar) {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+                                MeasureSpec.EXACTLY));
+            } else {
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+            }
+
+            if (child.getVisibility() != View.GONE) {
+                totalHeight += child.getMeasuredHeight();
+            }
+        }
+
+        // Use the new width so it's centered horizontally
+        setMeasuredDimension(newWidth, totalHeight);
+
+        mMediumHeight = totalHeight;
+        mMediumWidth = getMeasuredWidth();
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        onLayoutInternal();
+    }
+
+    /**
+     * Contains all the testable logic that should be invoked when
+     * {@link #onLayout(boolean, int, int, int, int)}, is invoked.
+     */
+    @VisibleForTesting
+    void onLayoutInternal() {
+        // Start with initial size only once. Subsequent layout changes don't matter since we
+        // only care about the initial icon position.
+        if (mIconOriginalY == 0) {
+            mIconOriginalY = mIconView.getY();
+            if (mSavedState == null) {
+                updateSize(!mRequireConfirmation && supportsSmallDialog() ? AuthDialog.SIZE_SMALL
+                        : AuthDialog.SIZE_MEDIUM);
+            } else {
+                updateSize(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE));
+
+                // Restore indicator text state only after size has been restored
+                final String indicatorText =
+                        mSavedState.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING);
+                if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING)) {
+                    onHelp(indicatorText);
+                } else if (mSavedState.getBoolean(
+                        AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)) {
+                    onAuthenticationFailed(indicatorText);
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
new file mode 100644
index 0000000..6555c75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricAuthenticator;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Top level container/controller for the BiometricPrompt UI.
+ */
+public class AuthContainerView extends LinearLayout
+        implements AuthDialog, WakefulnessLifecycle.Observer {
+
+    private static final String TAG = "BiometricPrompt/AuthContainerView";
+    private static final int ANIMATION_DURATION_SHOW_MS = 250;
+    private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+
+    static final int STATE_UNKNOWN = 0;
+    static final int STATE_ANIMATING_IN = 1;
+    static final int STATE_PENDING_DISMISS = 2;
+    static final int STATE_SHOWING = 3;
+    static final int STATE_ANIMATING_OUT = 4;
+    static final int STATE_GONE = 5;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
+            STATE_ANIMATING_OUT, STATE_GONE})
+    @interface ContainerState {}
+
+    final Config mConfig;
+    private final IBinder mWindowToken = new Binder();
+    private final WindowManager mWindowManager;
+    private final AuthPanelController mPanelController;
+    private final Interpolator mLinearOutSlowIn;
+    @VisibleForTesting final BiometricCallback mBiometricCallback;
+
+    private final ViewGroup mContainerView;
+    private final AuthBiometricView mBiometricView;
+
+    private final ImageView mBackgroundView;
+    private final ScrollView mScrollView;
+    private final View mPanelView;
+
+    private final float mTranslationY;
+
+    @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
+
+    private @ContainerState int mContainerState = STATE_UNKNOWN;
+
+    // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
+    @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
+
+    static class Config {
+        Context mContext;
+        AuthDialogCallback mCallback;
+        Bundle mBiometricPromptBundle;
+        boolean mRequireConfirmation;
+        int mUserId;
+        String mOpPackageName;
+        int mModalityMask;
+        boolean mSkipIntro;
+    }
+
+    public static class Builder {
+        Config mConfig;
+
+        public Builder(Context context) {
+            mConfig = new Config();
+            mConfig.mContext = context;
+        }
+
+        public Builder setCallback(AuthDialogCallback callback) {
+            mConfig.mCallback = callback;
+            return this;
+        }
+
+        public Builder setBiometricPromptBundle(Bundle bundle) {
+            mConfig.mBiometricPromptBundle = bundle;
+            return this;
+        }
+
+        public Builder setRequireConfirmation(boolean requireConfirmation) {
+            mConfig.mRequireConfirmation = requireConfirmation;
+            return this;
+        }
+
+        public Builder setUserId(int userId) {
+            mConfig.mUserId = userId;
+            return this;
+        }
+
+        public Builder setOpPackageName(String opPackageName) {
+            mConfig.mOpPackageName = opPackageName;
+            return this;
+        }
+
+        public Builder setSkipIntro(boolean skip) {
+            mConfig.mSkipIntro = skip;
+            return this;
+        }
+
+        public AuthContainerView build(int modalityMask) {
+            mConfig.mModalityMask = modalityMask;
+            return new AuthContainerView(mConfig);
+        }
+    }
+
+    @VisibleForTesting
+    final class BiometricCallback implements AuthBiometricView.Callback {
+        @Override
+        public void onAction(int action) {
+            switch (action) {
+                case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
+                    animateAway(AuthDialogCallback.DISMISSED_AUTHENTICATED);
+                    break;
+                case AuthBiometricView.Callback.ACTION_USER_CANCELED:
+                    animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+                    break;
+                case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
+                    animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+                    break;
+                case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+                    mConfig.mCallback.onTryAgainPressed();
+                    break;
+                case AuthBiometricView.Callback.ACTION_ERROR:
+                    animateAway(AuthDialogCallback.DISMISSED_ERROR);
+                    break;
+                default:
+                    Log.e(TAG, "Unhandled action: " + action);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    AuthContainerView(Config config) {
+        super(config.mContext);
+
+        mConfig = config;
+        mWindowManager = mContext.getSystemService(WindowManager.class);
+        mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+
+        mTranslationY = getResources()
+                .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
+        mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
+        mBiometricCallback = new BiometricCallback();
+
+        final LayoutInflater factory = LayoutInflater.from(mContext);
+        mContainerView = (ViewGroup) factory.inflate(
+                R.layout.auth_container_view, this, false /* attachToRoot */);
+
+        mPanelView = mContainerView.findViewById(R.id.panel);
+        mPanelController = new AuthPanelController(mContext, mPanelView);
+
+        // TODO: Update with new controllers if multi-modal authentication can occur simultaneously
+        if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
+            mBiometricView = (AuthBiometricFingerprintView)
+                    factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
+        } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
+            mBiometricView = (AuthBiometricFaceView)
+                    factory.inflate(R.layout.auth_biometric_face_view, null, false);
+        } else {
+            Log.e(TAG, "Unsupported modality mask: " + config.mModalityMask);
+            mBiometricView = null;
+            mBackgroundView = null;
+            mScrollView = null;
+            return;
+        }
+
+        mBackgroundView = mContainerView.findViewById(R.id.background);
+
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (userManager.isManagedProfile(mConfig.mUserId)) {
+            final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
+                    mContext.getTheme());
+            image.setColorFilter(dpm.getOrganizationColorForUser(mConfig.mUserId),
+                    PorterDuff.Mode.DARKEN);
+            mBackgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+            mBackgroundView.setImageDrawable(image);
+        }
+
+        mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
+        mBiometricView.setPanelController(mPanelController);
+        mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle);
+        mBiometricView.setCallback(mBiometricCallback);
+        mBiometricView.setBackgroundView(mBackgroundView);
+
+        mScrollView = mContainerView.findViewById(R.id.scrollview);
+        mScrollView.addView(mBiometricView);
+        addView(mContainerView);
+
+        setOnKeyListener((v, keyCode, event) -> {
+            if (keyCode != KeyEvent.KEYCODE_BACK) {
+                return false;
+            }
+            if (event.getAction() == KeyEvent.ACTION_UP) {
+                animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+            }
+            return true;
+        });
+
+        setFocusableInTouchMode(true);
+        requestFocus();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mWakefulnessLifecycle.addObserver(this);
+
+        if (mConfig.mSkipIntro) {
+            mContainerState = STATE_SHOWING;
+        } else {
+            mContainerState = STATE_ANIMATING_IN;
+            // The background panel and content are different views since we need to be able to
+            // animate them separately in other places.
+            mPanelView.setY(mTranslationY);
+            mScrollView.setY(mTranslationY);
+
+            setAlpha(0f);
+            postOnAnimation(() -> {
+                mPanelView.animate()
+                        .translationY(0)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .withEndAction(this::onDialogAnimatedIn)
+                        .start();
+                mScrollView.animate()
+                        .translationY(0)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .start();
+                animate()
+                        .alpha(1f)
+                        .setDuration(ANIMATION_DURATION_SHOW_MS)
+                        .setInterpolator(mLinearOutSlowIn)
+                        .withLayer()
+                        .start();
+            });
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mWakefulnessLifecycle.removeObserver(this);
+    }
+
+    @Override
+    public void onStartedGoingToSleep() {
+        animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+    }
+
+    @Override
+    public void show(WindowManager wm, @Nullable Bundle savedState) {
+        mBiometricView.restoreState(savedState);
+        wm.addView(this, getLayoutParams(mWindowToken));
+    }
+
+    @Override
+    public void dismissWithoutCallback(boolean animate) {
+        if (animate) {
+            animateAway(false /* sendReason */, 0 /* reason */);
+        } else {
+            removeWindowIfAttached();
+        }
+    }
+
+    @Override
+    public void dismissFromSystemServer() {
+        removeWindowIfAttached();
+    }
+
+    @Override
+    public void onAuthenticationSucceeded() {
+        mBiometricView.onAuthenticationSucceeded();
+    }
+
+    @Override
+    public void onAuthenticationFailed(String failureReason) {
+        mBiometricView.onAuthenticationFailed(failureReason);
+    }
+
+    @Override
+    public void onHelp(String help) {
+        mBiometricView.onHelp(help);
+    }
+
+    @Override
+    public void onError(String error) {
+        mBiometricView.onError(error);
+    }
+
+    @Override
+    public void onSaveState(@NonNull Bundle outState) {
+        outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
+        mBiometricView.onSaveState(outState);
+    }
+
+    @Override
+    public String getOpPackageName() {
+        return mConfig.mOpPackageName;
+    }
+
+    @VisibleForTesting
+    void animateAway(int reason) {
+        animateAway(true /* sendReason */, reason);
+    }
+
+    private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
+        if (mContainerState == STATE_ANIMATING_IN) {
+            Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
+            mContainerState = STATE_PENDING_DISMISS;
+            return;
+        }
+
+        if (mContainerState == STATE_ANIMATING_OUT) {
+            Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
+            return;
+        }
+        mContainerState = STATE_ANIMATING_OUT;
+
+        if (sendReason) {
+            mPendingCallbackReason = reason;
+        } else {
+            mPendingCallbackReason = null;
+        }
+
+        final Runnable endActionRunnable = () -> {
+            setVisibility(View.INVISIBLE);
+            removeWindowIfAttached();
+        };
+
+        postOnAnimation(() -> {
+            mPanelView.animate()
+                    .translationY(mTranslationY)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .withEndAction(endActionRunnable)
+                    .start();
+            mScrollView.animate()
+                    .translationY(mTranslationY)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .start();
+            animate()
+                    .alpha(0f)
+                    .setDuration(ANIMATION_DURATION_AWAY_MS)
+                    .setInterpolator(mLinearOutSlowIn)
+                    .withLayer()
+                    .start();
+        });
+    }
+
+    private void sendPendingCallbackIfNotNull() {
+        Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
+        if (mPendingCallbackReason != null) {
+            mConfig.mCallback.onDismissed(mPendingCallbackReason);
+            mPendingCallbackReason = null;
+        }
+    }
+
+    private void removeWindowIfAttached() {
+        sendPendingCallbackIfNotNull();
+
+        if (mContainerState == STATE_GONE) {
+            return;
+        }
+        mContainerState = STATE_GONE;
+        mWindowManager.removeView(this);
+    }
+
+    private void onDialogAnimatedIn() {
+        if (mContainerState == STATE_PENDING_DISMISS) {
+            Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
+            animateAway(false /* sendReason */, 0);
+            return;
+        }
+        mContainerState = STATE_SHOWING;
+        mBiometricView.onDialogAnimatedIn();
+    }
+
+    /**
+     * @param windowToken token for the window
+     * @return
+     */
+    public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("BiometricPrompt");
+        lp.token = windowToken;
+        return lp;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
rename to packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a8e5722..d10a3fe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.biometrics;
@@ -35,7 +35,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.systemui.SystemUI;
-import com.android.systemui.biometrics.ui.BiometricDialogView;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.util.List;
@@ -44,9 +43,10 @@
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
  */
-public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
-        DialogViewCallback {
-    private static final String TAG = "BiometricDialogImpl";
+public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+        AuthDialogCallback {
+
+    private static final String TAG = "BiometricPrompt/AuthController";
     private static final boolean DEBUG = true;
 
     private final Injector mInjector;
@@ -54,7 +54,7 @@
     // TODO: These should just be saved from onSaveState
     private SomeArgs mCurrentDialogArgs;
     @VisibleForTesting
-    BiometricDialog mCurrentDialog;
+    AuthDialog mCurrentDialog;
 
     private Handler mHandler = new Handler(Looper.getMainLooper());
     private WindowManager mWindowManager;
@@ -107,27 +107,27 @@
     @Override
     public void onDismissed(@DismissedReason int reason) {
         switch (reason) {
-            case DialogViewCallback.DISMISSED_USER_CANCELED:
+            case AuthDialogCallback.DISMISSED_USER_CANCELED:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
                 break;
 
-            case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
+            case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
                 break;
 
-            case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
+            case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
                 break;
 
-            case DialogViewCallback.DISMISSED_AUTHENTICATED:
+            case AuthDialogCallback.DISMISSED_AUTHENTICATED:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
                 break;
 
-            case DialogViewCallback.DISMISSED_ERROR:
+            case AuthDialogCallback.DISMISSED_ERROR:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
                 break;
 
-            case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
+            case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
                 break;
 
@@ -156,12 +156,12 @@
         }
     }
 
-    public BiometricDialogImpl() {
+    public AuthController() {
         this(new Injector());
     }
 
     @VisibleForTesting
-    BiometricDialogImpl(Injector injector) {
+    AuthController(Injector injector) {
         mInjector = injector;
     }
 
@@ -248,12 +248,13 @@
         final String opPackageName = (String) args.arg4;
 
         // Create a new dialog but do not replace the current one yet.
-        final BiometricDialog newDialog = buildDialog(
+        final AuthDialog newDialog = buildDialog(
                 biometricPromptBundle,
                 requireConfirmation,
                 userId,
                 type,
-                opPackageName);
+                opPackageName,
+                skipAnimation);
 
         if (newDialog == null) {
             Log.e(TAG, "Unsupported type: " + type);
@@ -268,11 +269,7 @@
                     + " type: " + type);
         }
 
-        if (savedState != null) {
-            // SavedState is only non-null if it's from onConfigurationChanged. Restore the state
-            // even though it may be removed / re-created again
-            newDialog.restoreState(savedState);
-        } else if (mCurrentDialog != null) {
+        if (mCurrentDialog != null) {
             // If somehow we're asked to show a dialog, the old one doesn't need to be animated
             // away. This can happen if the app cancels and re-starts auth during configuration
             // change. This is ugly because we also have to do things on onConfigurationChanged
@@ -282,7 +279,7 @@
 
         mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
         mCurrentDialog = newDialog;
-        mCurrentDialog.show(mWindowManager, skipAnimation);
+        mCurrentDialog.show(mWindowManager, savedState);
     }
 
     private void onDialogDismissed(@DismissedReason int reason) {
@@ -305,18 +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 BiometricDialog buildDialog(Bundle biometricPromptBundle,
-            boolean requireConfirmation, int userId, int type, String opPackageName) {
-        return new BiometricDialogView.Builder(mContext)
+    protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
+            int userId, int type, String opPackageName, boolean skipIntro) {
+        return new AuthContainerView.Builder(mContext)
                 .setCallback(this)
                 .setBiometricPromptBundle(biometricPromptBundle)
                 .setRequireConfirmation(requireConfirmation)
                 .setUserId(userId)
                 .setOpPackageName(opPackageName)
+                .setSkipIntro(skipIntro)
                 .build(type);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
new file mode 100644
index 0000000..edb2953
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Interface for the biometric dialog UI.
+ */
+public interface AuthDialog {
+
+    String KEY_CONTAINER_STATE = "container_state";
+
+    String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
+    String KEY_BIOMETRIC_STATE = "state";
+    String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
+    String KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING = "error_is_temporary";
+    String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
+    String KEY_BIOMETRIC_DIALOG_SIZE = "size";
+
+    int SIZE_UNKNOWN = 0;
+    int SIZE_SMALL = 1;
+    int SIZE_MEDIUM = 2;
+    int SIZE_LARGE = 3;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
+    @interface DialogSize {}
+
+    /**
+     * Animation duration, e.g. small to medium dialog, icon translation, etc.
+     */
+    int ANIMATE_DURATION_MS = 150;
+
+    /**
+     * Show the dialog.
+     * @param wm
+     */
+    void show(WindowManager wm, @Nullable Bundle savedState);
+
+    /**
+     * Dismiss the dialog without sending a callback.
+     */
+    void dismissWithoutCallback(boolean animate);
+
+    /**
+     * Dismiss the dialog. Animate away.
+     */
+    void dismissFromSystemServer();
+
+    /**
+     * Biometric authenticated. May be pending user confirmation, or completed.
+     */
+    void onAuthenticationSucceeded();
+
+    /**
+     * Authentication failed (reject, timeout). Dialog stays showing.
+     * @param failureReason
+     */
+    void onAuthenticationFailed(String failureReason);
+
+    /**
+     * Authentication rejected, or help message received.
+     * @param help
+     */
+    void onHelp(String help);
+
+    /**
+     * Authentication failed. Dialog going away.
+     * @param error
+     */
+    void onError(String error);
+
+    /**
+     * Save the current state.
+     * @param outState
+     */
+    void onSaveState(@NonNull Bundle outState);
+
+    /**
+     * Get the client's package name
+     */
+    String getOpPackageName();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
rename to packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index b65d1e8..70752f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.biometrics;
@@ -22,7 +22,7 @@
  * Callback interface for dialog views. These should be implemented by the controller (e.g.
  * FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
  */
-public interface DialogViewCallback {
+public interface AuthDialogCallback {
 
     int DISMISSED_USER_CANCELED = 1;
     int DISMISSED_BUTTON_NEGATIVE = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
new file mode 100644
index 0000000..55ba049
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthDialog;
+
+/**
+ * Controls the back panel and its animations for the BiometricPrompt UI.
+ */
+public class AuthPanelController extends ViewOutlineProvider {
+
+    private static final String TAG = "BiometricPrompt/AuthPanelController";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final View mPanelView;
+    private final float mCornerRadius;
+    private final int mBiometricMargin;
+
+    private int mContainerWidth;
+    private int mContainerHeight;
+
+    private int mContentWidth;
+    private int mContentHeight;
+
+    @Override
+    public void getOutline(View view, Outline outline) {
+        final int left = (mContainerWidth - mContentWidth) / 2;
+        final int right = mContainerWidth - left;
+        final int top = mContentHeight < mContainerHeight
+                ? mContainerHeight - mContentHeight - mBiometricMargin
+                : mBiometricMargin;
+        final int bottom = mContainerHeight - mBiometricMargin;
+        outline.setRoundRect(left, top, right, bottom, mCornerRadius);
+    }
+
+    public void setContainerDimensions(int containerWidth, int containerHeight) {
+        if (DEBUG) {
+            Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight);
+        }
+        mContainerWidth = containerWidth;
+        mContainerHeight = containerHeight;
+    }
+
+    public void updateForContentDimensions(int contentWidth, int contentHeight, boolean animate) {
+        if (DEBUG) {
+            Log.v(TAG, "Content Width: " + contentWidth
+                    + " Height: " + contentHeight
+                    + " Animate: " + animate);
+        }
+
+        if (mContainerWidth == 0 || mContainerHeight == 0) {
+            Log.w(TAG, "Not done measuring yet");
+            return;
+        }
+
+        if (animate) {
+            ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight);
+            heightAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+            heightAnimator.addUpdateListener((animation) -> {
+                mContentHeight = (int) animation.getAnimatedValue();
+                mPanelView.invalidateOutline();
+            });
+            heightAnimator.start();
+        } else {
+            mContentWidth = contentWidth;
+            mContentHeight = contentHeight;
+            mPanelView.invalidateOutline();
+        }
+    }
+
+    AuthPanelController(Context context, View panelView) {
+        mContext = context;
+        mPanelView = panelView;
+        mCornerRadius = context.getResources()
+                .getDimension(R.dimen.biometric_dialog_corner_size);
+        mBiometricMargin = (int) context.getResources()
+                .getDimension(R.dimen.biometric_dialog_border_padding);
+        mPanelView.setOutlineProvider(this);
+        mPanelView.setClipToOutline(true);
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
deleted file mode 100644
index d4baefd..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import com.android.systemui.biometrics.ui.BiometricDialogView;
-
-/**
- * Interface for the biometric dialog UI.
- */
-public interface BiometricDialog {
-
-    // TODO: Clean up save/restore state
-    String[] KEYS_TO_BACKUP = {
-            BiometricPrompt.KEY_TITLE,
-            BiometricPrompt.KEY_USE_DEFAULT_TITLE,
-            BiometricPrompt.KEY_SUBTITLE,
-            BiometricPrompt.KEY_DESCRIPTION,
-            BiometricPrompt.KEY_POSITIVE_TEXT,
-            BiometricPrompt.KEY_NEGATIVE_TEXT,
-            BiometricPrompt.KEY_REQUIRE_CONFIRMATION,
-            BiometricPrompt.KEY_ALLOW_DEVICE_CREDENTIAL,
-            BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL,
-
-            BiometricDialogView.KEY_TRY_AGAIN_VISIBILITY,
-            BiometricDialogView.KEY_CONFIRM_VISIBILITY,
-            BiometricDialogView.KEY_CONFIRM_ENABLED,
-            BiometricDialogView.KEY_STATE,
-            BiometricDialogView.KEY_ERROR_TEXT_VISIBILITY,
-            BiometricDialogView.KEY_ERROR_TEXT_STRING,
-            BiometricDialogView.KEY_ERROR_TEXT_IS_TEMPORARY,
-            BiometricDialogView.KEY_ERROR_TEXT_COLOR,
-    };
-
-    /**
-     * Show the dialog.
-     * @param wm
-     * @param skipIntroAnimation
-     */
-    void show(WindowManager wm, boolean skipIntroAnimation);
-
-    /**
-     * Dismiss the dialog without sending a callback.
-     */
-    void dismissWithoutCallback(boolean animate);
-
-    /**
-     * Dismiss the dialog. Animate away.
-     */
-    void dismissFromSystemServer();
-
-    /**
-     * Biometric authenticated. May be pending user confirmation, or completed.
-     */
-    void onAuthenticationSucceeded();
-
-    /**
-     * Authentication failed (reject, timeout). Dialog stays showing.
-     * @param failureReason
-     */
-    void onAuthenticationFailed(String failureReason);
-
-    /**
-     * Authentication rejected, or help message received.
-     * @param help
-     */
-    void onHelp(String help);
-
-    /**
-     * Authentication failed. Dialog going away.
-     * @param error
-     */
-    void onError(String error);
-
-    /**
-     * Save the current state.
-     * @param outState
-     */
-    void onSaveState(Bundle outState);
-
-    /**
-     * Restore a previous state.
-     * @param savedState
-     */
-    void restoreState(Bundle savedState);
-
-    /**
-     * Get the client's package name
-     */
-    String getOpPackageName();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
new file mode 100644
index 0000000..e00cf6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+public class Utils {
+    static float dpToPixels(Context context, float dp) {
+        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    static float pixelsToDp(Context context, float pixels) {
+        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
+                / DisplayMetrics.DENSITY_DEFAULT);
+    }
+
+    static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
+        if (!am.isEnabled()) {
+            return;
+        }
+        AccessibilityEvent event = AccessibilityEvent.obtain();
+        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
+        view.sendAccessibilityEventUnchecked(event);
+        view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
deleted file mode 100644
index 2904755..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
+++ /dev/null
@@ -1,956 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.Interpolator;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.biometrics.BiometricDialog;
-import com.android.systemui.biometrics.DialogViewCallback;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.util.leak.RotationUtils;
-
-/**
- * Abstract base class. Shows a dialog for BiometricPrompt.
- */
-public abstract class BiometricDialogView extends LinearLayout implements BiometricDialog {
-
-    private static final String TAG = "BiometricDialogView";
-
-    public static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
-    public static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
-    public static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled";
-    public static final String KEY_STATE = "key_state";
-    public static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility";
-    public static final String KEY_ERROR_TEXT_STRING = "key_error_text_string";
-    public static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary";
-    public static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color";
-    public static final String KEY_DIALOG_SIZE = "key_dialog_size";
-
-    private static final int ANIMATION_DURATION_SHOW = 250; // ms
-    private static final int ANIMATION_DURATION_AWAY = 350; // ms
-
-    protected static final int MSG_RESET_MESSAGE = 1;
-
-    protected static final int STATE_IDLE = 0;
-    protected static final int STATE_AUTHENTICATING = 1;
-    protected static final int STATE_ERROR = 2;
-    protected static final int STATE_PENDING_CONFIRMATION = 3;
-    protected static final int STATE_AUTHENTICATED = 4;
-
-    // Dialog layout/animation
-    private static final int IMPLICIT_Y_PADDING = 16; // dp
-    private static final int GROW_DURATION = 150; // ms
-    private static final int TEXT_ANIMATE_DISTANCE = 32; // dp
-    @VisibleForTesting static final int SIZE_UNKNOWN = 0;
-    @VisibleForTesting static final int SIZE_SMALL = 1;
-    @VisibleForTesting static final int SIZE_GROWING = 2;
-    @VisibleForTesting static final int SIZE_BIG = 3;
-    @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_GROWING, SIZE_BIG})
-    @interface DialogSize {}
-
-    @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final AccessibilityManager mAccessibilityManager;
-    private final IBinder mWindowToken = new Binder();
-    private final Interpolator mLinearOutSlowIn;
-    private final WindowManager mWindowManager;
-    private final UserManager mUserManager;
-    private final DevicePolicyManager mDevicePolicyManager;
-    private final float mAnimationTranslationOffset;
-    private final int mErrorColor;
-    private final float mDialogWidth;
-    protected final DialogViewCallback mCallback;
-    private final DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
-
-    protected final ViewGroup mLayout;
-    protected final LinearLayout mDialog;
-    @VisibleForTesting final TextView mTitleText;
-    @VisibleForTesting final TextView mSubtitleText;
-    @VisibleForTesting final TextView mDescriptionText;
-    @VisibleForTesting final ImageView mBiometricIcon;
-    @VisibleForTesting final TextView mErrorText;
-    @VisibleForTesting final Button mPositiveButton;
-    @VisibleForTesting final Button mNegativeButton;
-    @VisibleForTesting final Button mTryAgainButton;
-
-    protected final int mTextColor;
-
-    private Bundle mBundle;
-    private Bundle mRestoredState;
-    private String mOpPackageName;
-
-    private int mState = STATE_IDLE;
-    private boolean mWasForceRemoved;
-    private boolean mSkipIntro;
-    protected boolean mRequireConfirmation;
-    private int mUserId; // used to determine if we should show work background
-    private @DialogSize int mSize;
-    private float mIconOriginalY;
-
-    private boolean mCompletedAnimatingIn;
-    private boolean mPendingDismissDialog;
-
-    protected abstract int getHintStringResourceId();
-    protected abstract int getAuthenticatedAccessibilityResourceId();
-    protected abstract int getIconDescriptionResourceId();
-    protected abstract int getDelayAfterAuthenticatedDurationMs();
-    protected abstract boolean shouldGrayAreaDismissDialog();
-    protected abstract void handleResetMessage();
-    protected abstract void updateIcon(int oldState, int newState);
-    protected abstract boolean supportsSmallDialog();
-
-    private final Runnable mShowAnimationRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mLayout.animate()
-                    .alpha(1f)
-                    .setDuration(ANIMATION_DURATION_SHOW)
-                    .setInterpolator(mLinearOutSlowIn)
-                    .withLayer()
-                    .start();
-            mDialog.animate()
-                    .translationY(0)
-                    .setDuration(ANIMATION_DURATION_SHOW)
-                    .setInterpolator(mLinearOutSlowIn)
-                    .withLayer()
-                    .withEndAction(() -> onDialogAnimatedIn())
-                    .start();
-        }
-    };
-
-    @VisibleForTesting
-    final WakefulnessLifecycle.Observer mWakefulnessObserver =
-            new WakefulnessLifecycle.Observer() {
-                @Override
-                public void onStartedGoingToSleep() {
-                    animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-                }
-            };
-
-    private final class DialogOutlineProvider extends ViewOutlineProvider {
-
-        float mY;
-
-        @Override
-        public void getOutline(View view, Outline outline) {
-            outline.setRoundRect(
-                    0 /* left */,
-                    (int) mY, /* top */
-                    mDialog.getWidth() /* right */,
-                    mDialog.getBottom(), /* bottom */
-                    getResources().getDimension(R.dimen.biometric_dialog_corner_size));
-        }
-
-        int calculateSmall() {
-            final float padding = Utils.dpToPixels(mContext, IMPLICIT_Y_PADDING);
-            return mDialog.getHeight() - mBiometricIcon.getHeight() - 2 * (int) padding;
-        }
-
-        void setOutlineY(float y) {
-            mY = y;
-        }
-    }
-
-    protected Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case MSG_RESET_MESSAGE:
-                    handleResetMessage();
-                    break;
-                default:
-                    Log.e(TAG, "Unhandled message: " + msg.what);
-                    break;
-            }
-        }
-    };
-
-    /**
-     * Builds the dialog with specified parameters.
-     */
-    public static class Builder {
-        public static final int TYPE_FINGERPRINT = BiometricAuthenticator.TYPE_FINGERPRINT;
-        public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE;
-
-        private Context mContext;
-        private DialogViewCallback mCallback;
-        private Bundle mBundle;
-        private boolean mRequireConfirmation;
-        private int mUserId;
-        private String mOpPackageName;
-
-        public Builder(Context context) {
-            mContext = context;
-        }
-
-        public Builder setCallback(DialogViewCallback callback) {
-            mCallback = callback;
-            return this;
-        }
-
-        public Builder setBiometricPromptBundle(Bundle bundle) {
-            mBundle = bundle;
-            return this;
-        }
-
-        public Builder setRequireConfirmation(boolean requireConfirmation) {
-            mRequireConfirmation = requireConfirmation;
-            return this;
-        }
-
-        public Builder setUserId(int userId) {
-            mUserId = userId;
-            return this;
-        }
-
-        public Builder setOpPackageName(String opPackageName) {
-            mOpPackageName = opPackageName;
-            return this;
-        }
-
-        public BiometricDialogView build(int type) {
-            return build(type, new Injector());
-        }
-
-        public BiometricDialogView build(int type, Injector injector) {
-            BiometricDialogView dialog;
-            if (type == TYPE_FINGERPRINT) {
-                dialog = new FingerprintDialogView(mContext, mCallback, injector);
-            } else if (type == TYPE_FACE) {
-                dialog = new FaceDialogView(mContext, mCallback, injector);
-            } else {
-                return null;
-            }
-            dialog.setBundle(mBundle);
-            dialog.setRequireConfirmation(mRequireConfirmation);
-            dialog.setUserId(mUserId);
-            dialog.setOpPackageName(mOpPackageName);
-            return dialog;
-        }
-    }
-
-    public static class Injector {
-        public WakefulnessLifecycle getWakefulnessLifecycle() {
-            return Dependency.get(WakefulnessLifecycle.class);
-        }
-    }
-
-    protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) {
-        super(context);
-        mWakefulnessLifecycle = injector.getWakefulnessLifecycle();
-
-        mCallback = callback;
-        mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
-        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
-        mWindowManager = mContext.getSystemService(WindowManager.class);
-        mUserManager = mContext.getSystemService(UserManager.class);
-        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
-        mAnimationTranslationOffset = getResources()
-                .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
-        mErrorColor = getResources().getColor(R.color.biometric_dialog_error);
-        mTextColor = getResources().getColor(R.color.biometric_dialog_gray);
-
-        DisplayMetrics metrics = new DisplayMetrics();
-        mWindowManager.getDefaultDisplay().getMetrics(metrics);
-        mDialogWidth = Math.min(metrics.widthPixels, metrics.heightPixels);
-
-        // Create the dialog
-        LayoutInflater factory = LayoutInflater.from(getContext());
-        mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
-        addView(mLayout);
-
-        mLayout.setOnKeyListener(new View.OnKeyListener() {
-            @Override
-            public boolean onKey(View v, int keyCode, KeyEvent event) {
-                if (keyCode != KeyEvent.KEYCODE_BACK) {
-                    return false;
-                }
-                if (event.getAction() == KeyEvent.ACTION_UP) {
-                    animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-                }
-                return true;
-            }
-        });
-
-        final View space = mLayout.findViewById(R.id.space);
-        final View leftSpace = mLayout.findViewById(R.id.left_space);
-        final View rightSpace = mLayout.findViewById(R.id.right_space);
-
-        mDialog = mLayout.findViewById(R.id.dialog);
-        mTitleText = mLayout.findViewById(R.id.title);
-        mSubtitleText = mLayout.findViewById(R.id.subtitle);
-        mDescriptionText = mLayout.findViewById(R.id.description);
-        mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
-        mErrorText = mLayout.findViewById(R.id.error);
-        mNegativeButton = mLayout.findViewById(R.id.button2);
-        mPositiveButton = mLayout.findViewById(R.id.button1);
-        mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
-
-        mBiometricIcon.setContentDescription(
-                getResources().getString(getIconDescriptionResourceId()));
-
-        setDismissesDialog(space);
-        setDismissesDialog(leftSpace);
-        setDismissesDialog(rightSpace);
-
-        mNegativeButton.setOnClickListener((View v) -> {
-            if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) {
-                animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-            } else {
-                animateAway(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
-            }
-        });
-
-        mPositiveButton.setOnClickListener((View v) -> {
-            updateState(STATE_AUTHENTICATED);
-            mHandler.postDelayed(() -> {
-                animateAway(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
-            }, getDelayAfterAuthenticatedDurationMs());
-        });
-
-        mTryAgainButton.setOnClickListener((View v) -> {
-            handleResetMessage();
-            updateState(STATE_AUTHENTICATING);
-            showTryAgainButton(false /* show */);
-
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(false);
-
-            mCallback.onTryAgainPressed();
-        });
-
-        // Must set these in order for the back button events to be received.
-        mLayout.setFocusableInTouchMode(true);
-        mLayout.requestFocus();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
-
-        final ImageView backgroundView = mLayout.findViewById(R.id.background);
-        if (mUserManager.isManagedProfile(mUserId)) {
-            final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
-                    mContext.getTheme());
-            image.setColorFilter(mDevicePolicyManager.getOrganizationColorForUser(mUserId),
-                    PorterDuff.Mode.DARKEN);
-            backgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
-            backgroundView.setImageDrawable(image);
-        } else {
-            backgroundView.setImageDrawable(null);
-            backgroundView.setBackgroundColor(R.color.biometric_dialog_dim_color);
-        }
-
-        mNegativeButton.setVisibility(View.VISIBLE);
-        mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
-
-        if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) {
-            mDialog.getLayoutParams().width = (int) mDialogWidth;
-        }
-
-        if (mRestoredState == null) {
-            updateState(STATE_AUTHENTICATING);
-            final int hint = getHintStringResourceId();
-            if (hint != 0) {
-                mErrorText.setText(hint);
-                mErrorText.setContentDescription(mContext.getString(hint));
-                mErrorText.setVisibility(View.VISIBLE);
-            } else {
-                mErrorText.setVisibility(View.INVISIBLE);
-            }
-            announceAccessibilityEvent();
-        } else {
-            updateState(mState);
-        }
-
-        CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
-
-        mTitleText.setVisibility(View.VISIBLE);
-        mTitleText.setText(titleText);
-
-        final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
-        if (TextUtils.isEmpty(subtitleText)) {
-            mSubtitleText.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        } else {
-            mSubtitleText.setVisibility(View.VISIBLE);
-            mSubtitleText.setText(subtitleText);
-        }
-
-        final CharSequence descriptionText =
-                mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
-        if (TextUtils.isEmpty(descriptionText)) {
-            mDescriptionText.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        } else {
-            mDescriptionText.setVisibility(View.VISIBLE);
-            mDescriptionText.setText(descriptionText);
-        }
-
-        if (requiresConfirmation() && mRestoredState == null) {
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(false);
-        }
-
-        if (mWasForceRemoved || mSkipIntro) {
-            // Show the dialog immediately
-            mLayout.animate().cancel();
-            mDialog.animate().cancel();
-            mDialog.setAlpha(1.0f);
-            mDialog.setTranslationY(0);
-            mLayout.setAlpha(1.0f);
-            mCompletedAnimatingIn = true;
-        } else {
-            // Dim the background and slide the dialog up
-            mDialog.setTranslationY(mAnimationTranslationOffset);
-            mLayout.setAlpha(0f);
-            postOnAnimation(mShowAnimationRunnable);
-        }
-        mWasForceRemoved = false;
-        mSkipIntro = false;
-    }
-
-    /**
-     * Do small/big layout here instead of onAttachedToWindow, since:
-     * 1) We need the big layout to be measured, etc for small -> big animation
-     * 2) We need the dialog measurements to know where to move the biometric icon to
-     *
-     * BiometricDialogView already sets the views to their default big state, so here we only
-     * need to hide the ones that are unnecessary.
-     */
-    @Override
-    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        if (mIconOriginalY == 0) {
-            mIconOriginalY = mBiometricIcon.getY();
-        }
-
-        // UNKNOWN means size hasn't been set yet. First time we create the dialog.
-        // onLayout can happen when visibility of views change (during animation, etc).
-        if (getSize() != SIZE_UNKNOWN) {
-            // Probably not the cleanest way to do this, but since dialog is big by default,
-            // and small dialogs can persist across orientation changes, we need to set it to
-            // small size here again.
-            if (getSize() == SIZE_SMALL) {
-                updateSize(SIZE_SMALL);
-            }
-            return;
-        }
-
-        // If we don't require confirmation, show the small dialog first (until errors occur).
-        if (!requiresConfirmation() && supportsSmallDialog()) {
-            updateSize(SIZE_SMALL);
-        } else {
-            updateSize(SIZE_BIG);
-        }
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
-    }
-
-    @VisibleForTesting
-    void updateSize(@DialogSize int newSize) {
-        final float padding = Utils.dpToPixels(mContext, IMPLICIT_Y_PADDING);
-        final float iconSmallPositionY = mDialog.getHeight() - mBiometricIcon.getHeight() - padding;
-
-        if (newSize == SIZE_SMALL) {
-            if (!supportsSmallDialog()) {
-                Log.e(TAG, "Small dialog unsupported");
-                return;
-            }
-
-            // These fields are required and/or always hold a spot on the UI, so should be set to
-            // INVISIBLE so they keep their position
-            mTitleText.setVisibility(View.INVISIBLE);
-            mErrorText.setVisibility(View.INVISIBLE);
-            mNegativeButton.setVisibility(View.INVISIBLE);
-
-            // These fields are optional, so set them to gone or invisible depending on their
-            // usage. If they're empty, they're already set to GONE in BiometricDialogView.
-            if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                mSubtitleText.setVisibility(View.INVISIBLE);
-            }
-            if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                mDescriptionText.setVisibility(View.INVISIBLE);
-            }
-
-            // Move the biometric icon to the small spot
-            mBiometricIcon.setY(iconSmallPositionY);
-
-            // Clip the dialog to the small size
-            mDialog.setOutlineProvider(mOutlineProvider);
-            mOutlineProvider.setOutlineY(mOutlineProvider.calculateSmall());
-
-            mDialog.setClipToOutline(true);
-            mDialog.invalidateOutline();
-
-            mSize = newSize;
-            announceAccessibilityEvent();
-        } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) {
-            mSize = SIZE_GROWING;
-
-            // Animate the outline
-            final ValueAnimator outlineAnimator =
-                    ValueAnimator.ofFloat(mOutlineProvider.calculateSmall(), 0);
-            outlineAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mOutlineProvider.setOutlineY(y);
-                mDialog.invalidateOutline();
-            });
-
-            // Animate the icon back to original big position
-            final ValueAnimator iconAnimator =
-                    ValueAnimator.ofFloat(iconSmallPositionY, mIconOriginalY);
-            iconAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mBiometricIcon.setY(y);
-            });
-
-            // Animate the error text so it slides up with the icon
-            final ValueAnimator textSlideAnimator =
-                    ValueAnimator.ofFloat(Utils.dpToPixels(mContext, TEXT_ANIMATE_DISTANCE), 0);
-            textSlideAnimator.addUpdateListener((animation) -> {
-                final float y = (float) animation.getAnimatedValue();
-                mErrorText.setTranslationY(y);
-            });
-
-            // Opacity animator for things that should fade in (title, subtitle, details, negative
-            // button)
-            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
-            opacityAnimator.addUpdateListener((animation) -> {
-                final float opacity = (float) animation.getAnimatedValue();
-
-                // These fields are required and/or always hold a spot on the UI
-                mTitleText.setAlpha(opacity);
-                mErrorText.setAlpha(opacity);
-                mNegativeButton.setAlpha(opacity);
-                mTryAgainButton.setAlpha(opacity);
-
-                // These fields are optional, so only animate them if they're supposed to be showing
-                if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                    mSubtitleText.setAlpha(opacity);
-                }
-                if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                    mDescriptionText.setAlpha(opacity);
-                }
-            });
-
-            // Choreograph together
-            final AnimatorSet as = new AnimatorSet();
-            as.setDuration(GROW_DURATION);
-            as.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    super.onAnimationStart(animation);
-                    // Set the visibility of opacity-animating views back to VISIBLE
-                    mTitleText.setVisibility(View.VISIBLE);
-                    mErrorText.setVisibility(View.VISIBLE);
-                    mNegativeButton.setVisibility(View.VISIBLE);
-                    mTryAgainButton.setVisibility(View.VISIBLE);
-
-                    if (!TextUtils.isEmpty(mSubtitleText.getText())) {
-                        mSubtitleText.setVisibility(View.VISIBLE);
-                    }
-                    if (!TextUtils.isEmpty(mDescriptionText.getText())) {
-                        mDescriptionText.setVisibility(View.VISIBLE);
-                    }
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    mSize = SIZE_BIG;
-                }
-            });
-            as.play(outlineAnimator).with(iconAnimator).with(opacityAnimator)
-                    .with(textSlideAnimator);
-            as.start();
-        } else if (mSize == SIZE_BIG) {
-            mDialog.setClipToOutline(false);
-            mDialog.invalidateOutline();
-
-            mBiometricIcon.setY(mIconOriginalY);
-
-            mSize = newSize;
-        }
-    }
-
-    private void setDismissesDialog(View v) {
-        v.setClickable(true);
-        v.setOnClickListener(v1 -> {
-            if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
-                animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
-            }
-        });
-    }
-
-    private void animateAway(@DialogViewCallback.DismissedReason int reason) {
-        animateAway(true /* sendReason */, reason);
-    }
-
-    /**
-     * Animate the dialog away
-     * @param reason one of the {@link DialogViewCallback} codes
-     */
-    private void animateAway(boolean sendReason, @DialogViewCallback.DismissedReason int reason) {
-        if (!mCompletedAnimatingIn) {
-            Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
-            mPendingDismissDialog = true;
-            return;
-        }
-
-        // This is where final cleanup should occur.
-        final Runnable endActionRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mWindowManager.removeView(BiometricDialogView.this);
-                // Set the icons / text back to normal state
-                handleResetMessage();
-                showTryAgainButton(false /* show */);
-                updateState(STATE_IDLE);
-                if (sendReason) {
-                    mCallback.onDismissed(reason);
-                }
-            }
-        };
-
-        postOnAnimation(new Runnable() {
-            @Override
-            public void run() {
-                mLayout.animate()
-                        .alpha(0f)
-                        .setDuration(ANIMATION_DURATION_AWAY)
-                        .setInterpolator(mLinearOutSlowIn)
-                        .withLayer()
-                        .start();
-                mDialog.animate()
-                        .translationY(mAnimationTranslationOffset)
-                        .setDuration(ANIMATION_DURATION_AWAY)
-                        .setInterpolator(mLinearOutSlowIn)
-                        .withLayer()
-                        .withEndAction(endActionRunnable)
-                        .start();
-            }
-        });
-    }
-
-    /**
-     * Skip the intro animation
-     */
-    private void setSkipIntro(boolean skip) {
-        mSkipIntro = skip;
-    }
-
-    private void setBundle(Bundle bundle) {
-        mBundle = bundle;
-    }
-
-    private void setRequireConfirmation(boolean requireConfirmation) {
-        mRequireConfirmation = requireConfirmation;
-    }
-
-    protected boolean requiresConfirmation() {
-        return mRequireConfirmation;
-    }
-
-    private void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    private void setOpPackageName(String opPackageName) {
-        mOpPackageName = opPackageName;
-    }
-
-    // Shows an error/help message
-    protected void showTemporaryMessage(String message) {
-        mHandler.removeMessages(MSG_RESET_MESSAGE);
-        mErrorText.setText(message);
-        mErrorText.setTextColor(mErrorColor);
-        mErrorText.setContentDescription(message);
-        mErrorText.setVisibility(View.VISIBLE);
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
-                BiometricPrompt.HIDE_DIALOG_DELAY);
-    }
-
-    @Override
-    public void show(WindowManager wm, boolean skipIntroAnimation) {
-        setSkipIntro(skipIntroAnimation);
-        wm.addView(this, getLayoutParams(mWindowToken));
-    }
-
-    /**
-     * Force remove the window, cancelling any animation that's happening. This should only be
-     * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method
-     * will cause the dialog to show without an animation the next time it's attached.
-     */
-    @Override
-    public void dismissWithoutCallback(boolean animate) {
-        if (animate) {
-            animateAway(false /* sendReason */, 0 /* reason */);
-        } else {
-            mLayout.animate().cancel();
-            mDialog.animate().cancel();
-            mWindowManager.removeView(BiometricDialogView.this);
-            mWasForceRemoved = true;
-        }
-    }
-
-    @Override
-    public void dismissFromSystemServer() {
-        animateAway(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
-    }
-
-    @Override
-    public void onAuthenticationSucceeded() {
-        announceForAccessibility(getResources().getText(getAuthenticatedAccessibilityResourceId()));
-
-        if (requiresConfirmation()) {
-            updateState(STATE_PENDING_CONFIRMATION);
-        } else {
-            mHandler.postDelayed(() -> {
-                animateAway(DialogViewCallback.DISMISSED_AUTHENTICATED);
-            }, getDelayAfterAuthenticatedDurationMs());
-
-            updateState(STATE_AUTHENTICATED);
-        }
-    }
-
-
-    @Override
-    public void onAuthenticationFailed(String message) {
-        updateState(STATE_ERROR);
-        showTemporaryMessage(message);
-    }
-
-    /**
-     * Transient help message (acquire) is received, dialog stays showing. Sensor stays in
-     * "authenticating" state.
-     * @param message
-     */
-    @Override
-    public void onHelp(String message) {
-        updateState(STATE_ERROR);
-        showTemporaryMessage(message);
-    }
-
-    /**
-     * Hard error is received, dialog will be dismissed soon.
-     * @param error
-     */
-    @Override
-    public void onError(String error) {
-        // All error messages will cause the dialog to go from small -> big. Error messages
-        // are messages such as lockout, auth failed, etc.
-        if (mSize == SIZE_SMALL) {
-            updateSize(SIZE_BIG);
-        }
-
-        updateState(STATE_ERROR);
-        showTemporaryMessage(error);
-        showTryAgainButton(false /* show */);
-
-        mHandler.postDelayed(() -> {
-            animateAway(DialogViewCallback.DISMISSED_ERROR);
-        }, BiometricPrompt.HIDE_DIALOG_DELAY);
-    }
-
-
-    @Override
-    public void onSaveState(Bundle bundle) {
-        bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
-        bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
-        bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled());
-        bundle.putInt(KEY_STATE, mState);
-        bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility());
-        bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText());
-        bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE));
-        bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor());
-        bundle.putInt(KEY_DIALOG_SIZE, mSize);
-    }
-
-    @Override
-    public void restoreState(Bundle bundle) {
-        mRestoredState = bundle;
-
-        // Keep in mind that this happens before onAttachedToWindow()
-        mSize = bundle.getInt(KEY_DIALOG_SIZE);
-
-        final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY);
-        mTryAgainButton.setVisibility(tryAgainVisibility);
-        final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY);
-        mPositiveButton.setVisibility(confirmVisibility);
-        final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED);
-        mPositiveButton.setEnabled(confirmEnabled);
-        mState = bundle.getInt(KEY_STATE);
-        mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
-        mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
-        final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY);
-        mErrorText.setVisibility(errorTextVisibility);
-        if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE
-                || confirmVisibility == View.INVISIBLE) {
-            announceAccessibilityEvent();
-        }
-        mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
-        if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) {
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
-                    BiometricPrompt.HIDE_DIALOG_DELAY);
-        }
-    }
-
-    @Override
-    public String getOpPackageName() {
-        return mOpPackageName;
-    }
-
-    protected void updateState(int newState) {
-        if (newState == STATE_PENDING_CONFIRMATION) {
-            mHandler.removeMessages(MSG_RESET_MESSAGE);
-            mErrorText.setTextColor(mTextColor);
-            mErrorText.setText(R.string.biometric_dialog_tap_confirm);
-            mErrorText.setContentDescription(
-                    getResources().getString(R.string.biometric_dialog_tap_confirm));
-            mErrorText.setVisibility(View.VISIBLE);
-            announceAccessibilityEvent();
-            mPositiveButton.setVisibility(View.VISIBLE);
-            mPositiveButton.setEnabled(true);
-        } else if (newState == STATE_AUTHENTICATED) {
-            mPositiveButton.setVisibility(View.GONE);
-            mNegativeButton.setVisibility(View.GONE);
-            mErrorText.setVisibility(View.INVISIBLE);
-            announceAccessibilityEvent();
-        }
-
-        if (newState == STATE_PENDING_CONFIRMATION || newState == STATE_AUTHENTICATED) {
-            mNegativeButton.setText(R.string.cancel);
-            mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
-        }
-
-        updateIcon(mState, newState);
-        mState = newState;
-    }
-
-    protected @DialogSize int getSize() {
-        return mSize;
-    }
-
-    protected void showTryAgainButton(boolean show) {
-        if (show && getSize() == SIZE_SMALL) {
-            // Do not call super, we will nicely animate the alpha together with the rest
-            // of the elements in here.
-            updateSize(SIZE_BIG);
-        } else {
-            if (show) {
-                mTryAgainButton.setVisibility(View.VISIBLE);
-            } else {
-                mTryAgainButton.setVisibility(View.GONE);
-                announceAccessibilityEvent();
-            }
-        }
-
-        if (show) {
-            mPositiveButton.setVisibility(View.GONE);
-            announceAccessibilityEvent();
-        }
-    }
-
-    protected void onDialogAnimatedIn() {
-        mCompletedAnimatingIn = true;
-
-        if (mPendingDismissDialog) {
-            Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
-            animateAway(false /* sendReason */, 0);
-            mPendingDismissDialog = false;
-        }
-    }
-
-    /**
-     * @param windowToken token for the window
-     * @return
-     */
-    public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
-                PixelFormat.TRANSLUCENT);
-        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-        lp.setTitle("BiometricDialogView");
-        lp.token = windowToken;
-        return lp;
-    }
-
-    // Every time a view becomes invisible we need to announce an accessibility event.
-    // This is due to an issue in the framework, b/132298701 recommended this workaround.
-    protected void announceAccessibilityEvent() {
-        if (!mAccessibilityManager.isEnabled()) {
-            return;
-        }
-        AccessibilityEvent event = AccessibilityEvent.obtain();
-        event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
-        event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
-        mDialog.sendAccessibilityEventUnchecked(event);
-        mDialog.notifySubtreeAccessibilityStateChanged(mDialog, mDialog,
-                CONTENT_CHANGE_TYPE_SUBTREE);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
deleted file mode 100644
index 9e4fe24..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import android.content.Context;
-import android.graphics.drawable.Animatable2;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
-
-/**
- * This class loads the view for the system-provided dialog. The view consists of:
- * Application Icon, Title, Subtitle, Description, Biometric Icon, Error/Help message area,
- * and positive/negative buttons.
- */
-public class FaceDialogView extends BiometricDialogView {
-
-    private static final String TAG = "FaceDialogView";
-
-    private static final String KEY_DIALOG_ANIMATED_IN = "key_dialog_animated_in";
-
-    private static final int HIDE_DIALOG_DELAY = 500; // ms
-
-    private IconController mIconController;
-    private boolean mDialogAnimatedIn;
-
-    /**
-     * Class that handles the biometric icon animations.
-     */
-    private final class IconController extends Animatable2.AnimationCallback {
-
-        private boolean mLastPulseDirection; // false = dark to light, true = light to dark
-
-        int mState;
-
-        IconController() {
-            mState = STATE_IDLE;
-        }
-
-        public void animateOnce(int iconRes) {
-            animateIcon(iconRes, false);
-        }
-
-        public void showStatic(int iconRes) {
-            mBiometricIcon.setImageDrawable(mContext.getDrawable(iconRes));
-        }
-
-        public void startPulsing() {
-            mLastPulseDirection = false;
-            animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
-        }
-
-        public void showIcon(int iconRes) {
-            final Drawable drawable = mContext.getDrawable(iconRes);
-            mBiometricIcon.setImageDrawable(drawable);
-        }
-
-        private void animateIcon(int iconRes, boolean repeat) {
-            final AnimatedVectorDrawable icon =
-                    (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
-            mBiometricIcon.setImageDrawable(icon);
-            icon.forceAnimationOnUI();
-            if (repeat) {
-                icon.registerAnimationCallback(this);
-            }
-            icon.start();
-        }
-
-        private void pulseInNextDirection() {
-            int iconRes = mLastPulseDirection ? R.drawable.face_dialog_pulse_dark_to_light
-                    : R.drawable.face_dialog_pulse_light_to_dark;
-            animateIcon(iconRes, true /* repeat */);
-            mLastPulseDirection = !mLastPulseDirection;
-        }
-
-        @Override
-        public void onAnimationEnd(Drawable drawable) {
-            super.onAnimationEnd(drawable);
-
-            if (mState == STATE_AUTHENTICATING) {
-                // Still authenticating, pulse the icon
-                pulseInNextDirection();
-            }
-        }
-    }
-
-    private final Runnable mErrorToIdleAnimationRunnable = () -> {
-        updateState(STATE_IDLE);
-        mErrorText.setVisibility(View.INVISIBLE);
-        announceAccessibilityEvent();
-    };
-
-    protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) {
-        super(context, callback, injector);
-        mIconController = new IconController();
-    }
-
-    @Override
-    public void onSaveState(Bundle bundle) {
-        super.onSaveState(bundle);
-        bundle.putBoolean(KEY_DIALOG_ANIMATED_IN, mDialogAnimatedIn);
-    }
-
-
-    @Override
-    protected void handleResetMessage() {
-        mErrorText.setTextColor(mTextColor);
-        mErrorText.setVisibility(View.INVISIBLE);
-        announceAccessibilityEvent();
-    }
-
-    @Override
-    public void restoreState(Bundle bundle) {
-        super.restoreState(bundle);
-        mDialogAnimatedIn = bundle.getBoolean(KEY_DIALOG_ANIMATED_IN);
-    }
-
-    @Override
-    public void onAuthenticationFailed(String message) {
-        super.onAuthenticationFailed(message);
-        showTryAgainButton(true);
-    }
-
-    @Override
-    protected int getHintStringResourceId() {
-        return 0;
-    }
-
-    @Override
-    protected int getAuthenticatedAccessibilityResourceId() {
-        if (mRequireConfirmation) {
-            return com.android.internal.R.string.face_authenticated_confirmation_required;
-        } else {
-            return com.android.internal.R.string.face_authenticated_no_confirmation_required;
-        }
-    }
-
-    @Override
-    protected int getIconDescriptionResourceId() {
-        return R.string.accessibility_face_dialog_face_icon;
-    }
-
-    @Override
-    protected void updateIcon(int oldState, int newState) {
-        mIconController.mState = newState;
-
-        if (newState == STATE_AUTHENTICATING) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            if (mDialogAnimatedIn) {
-                mIconController.startPulsing();
-            } else {
-                mIconController.showIcon(R.drawable.face_dialog_pulse_dark_to_light);
-            }
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticating));
-        } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_confirmed));
-        } else if (oldState == STATE_ERROR && newState == STATE_IDLE) {
-            mIconController.animateOnce(R.drawable.face_dialog_error_to_idle);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_idle));
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_ERROR) {
-            // It's easier to only check newState and gate showing the animation on the
-            // mErrorToIdleAnimationRunnable as a proxy, than add a ton of extra state. For example,
-            // we may go from error -> error due to configuration change which is valid and we
-            // should show the animation, or we can go from error -> error by receiving repeated
-            // acquire messages in which case we do not want to repeatedly start the animation.
-            if (!mHandler.hasCallbacks(mErrorToIdleAnimationRunnable)) {
-                mIconController.animateOnce(R.drawable.face_dialog_dark_to_error);
-                mHandler.postDelayed(mErrorToIdleAnimationRunnable,
-                        BiometricPrompt.HIDE_DIALOG_DELAY);
-            }
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_PENDING_CONFIRMATION) {
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_authenticated));
-        } else if (newState == STATE_IDLE) {
-            mIconController.showStatic(R.drawable.face_dialog_idle_static);
-            mBiometricIcon.setContentDescription(mContext.getString(
-                    R.string.biometric_dialog_face_icon_description_idle));
-        } else {
-            Log.w(TAG, "Unknown animation from " + oldState + " -> " + newState);
-        }
-
-        // Note that this must be after the newState == STATE_ERROR check above since this affects
-        // the logic.
-        if (oldState == STATE_ERROR && newState == STATE_ERROR) {
-            // Keep the error icon and text around for a while longer if we keep receiving
-            // STATE_ERROR
-            mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
-            mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
-        }
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return true;
-    }
-
-    @Override
-    public void onDialogAnimatedIn() {
-        super.onDialogAnimatedIn();
-        mDialogAnimatedIn = true;
-        mIconController.startPulsing();
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return HIDE_DIALOG_DELAY;
-    }
-
-    @Override
-    protected boolean shouldGrayAreaDismissDialog() {
-        if (getSize() == SIZE_SMALL) {
-            return false;
-        }
-        return true;
-    }
-
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
deleted file mode 100644
index 2925880..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import android.content.Context;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-
-import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
-
-/**
- * This class loads the view for the system-provided dialog. The view consists of:
- * Application Icon, Title, Subtitle, Description, Biometric Icon, Error/Help message area,
- * and positive/negative buttons.
- */
-public class FingerprintDialogView extends BiometricDialogView {
-
-    private static final String TAG = "FingerprintDialogView";
-
-    protected FingerprintDialogView(Context context, DialogViewCallback callback,
-            Injector injector) {
-        super(context, callback, injector);
-    }
-
-    @Override
-    protected void handleResetMessage() {
-        updateState(STATE_AUTHENTICATING);
-        mErrorText.setText(getHintStringResourceId());
-        mErrorText.setTextColor(mTextColor);
-    }
-
-    @Override
-    protected int getHintStringResourceId() {
-        return R.string.fingerprint_dialog_touch_sensor;
-    }
-
-    @Override
-    protected int getAuthenticatedAccessibilityResourceId() {
-        return com.android.internal.R.string.fingerprint_authenticated;
-    }
-
-    @Override
-    protected int getIconDescriptionResourceId() {
-        return R.string.accessibility_fingerprint_dialog_fingerprint_icon;
-    }
-
-    @Override
-    protected void updateIcon(int lastState, int newState) {
-        final Drawable icon = getAnimationForTransition(lastState, newState);
-        if (icon == null) {
-            Log.e(TAG, "Animation not found, " + lastState + " -> " + newState);
-            return;
-        }
-
-        final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable
-                ? (AnimatedVectorDrawable) icon
-                : null;
-
-        mBiometricIcon.setImageDrawable(icon);
-
-        if (animation != null && shouldAnimateForTransition(lastState, newState)) {
-            animation.forceAnimationOnUI();
-            animation.start();
-        }
-    }
-
-    @Override
-    protected boolean supportsSmallDialog() {
-        return false;
-    }
-
-    protected boolean shouldAnimateForTransition(int oldState, int newState) {
-        if (newState == STATE_ERROR) {
-            return true;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) {
-            return true;
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            return false;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            return false;
-        } else if (newState == STATE_AUTHENTICATING) {
-            return false;
-        }
-        return false;
-    }
-
-    @Override
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return 0;
-    }
-
-    @Override
-    protected boolean shouldGrayAreaDismissDialog() {
-        // Fingerprint dialog always dismisses when region outside the dialog is tapped
-        return true;
-    }
-
-    protected Drawable getAnimationForTransition(int oldState, int newState) {
-        int iconRes;
-        if (newState == STATE_ERROR) {
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) {
-            iconRes = R.drawable.fingerprint_dialog_error_to_fp;
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
-            // TODO(b/77328470): add animation when fingerprint is authenticated
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else if (newState == STATE_AUTHENTICATING) {
-            iconRes = R.drawable.fingerprint_dialog_fp_to_error;
-        } else {
-            return null;
-        }
-        return mContext.getDrawable(iconRes);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java
deleted file mode 100644
index 028b1aa..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import android.content.Context;
-import android.util.DisplayMetrics;
-
-public class Utils {
-    static float dpToPixels(Context context, float dp) {
-        return dp * ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-
-    static float pixelsToDp(Context context, float pixels) {
-        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
-                / DisplayMetrics.DENSITY_DEFAULT);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
deleted file mode 100644
index 74ad0fa..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.bubbles;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.systemui.R;
-
-// XXX: Mostly opied from launcher code / can we share?
-/**
- * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
- */
-public class BadgeRenderer {
-
-    private static final String TAG = "BadgeRenderer";
-
-    /** The badge sizes are defined as percentages of the app icon size. */
-    private static final float SIZE_PERCENTAGE = 0.38f;
-
-    /** Extra scale down of the dot. */
-    private static final float DOT_SCALE = 0.6f;
-
-    private final float mDotCenterOffset;
-    private final float mCircleRadius;
-    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
-    public BadgeRenderer(Context context) {
-        mDotCenterOffset = getDotCenterOffset(context);
-        mCircleRadius = getDotRadius(mDotCenterOffset);
-    }
-
-    /** Space between the center of the dot and the top or left of the bubble stack. */
-    static float getDotCenterOffset(Context context) {
-        final int iconSizePx =
-                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        return SIZE_PERCENTAGE * iconSizePx;
-    }
-
-    static float getDotRadius(float dotCenterOffset) {
-        int size = (int) (DOT_SCALE * dotCenterOffset);
-        return size / 2f;
-    }
-
-    /**
-     * Draw a circle in the top right corner of the given bounds.
-     *
-     * @param color The color (based on the icon) to use for the badge.
-     * @param iconBounds The bounds of the icon being badged.
-     * @param badgeScale The progress of the animation, from 0 to 1.
-     * @param spaceForOffset How much space to offset the badge up and to the left or right.
-     * @param onLeft Whether the badge should be draw on left or right side.
-     */
-    public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
-            Point spaceForOffset, boolean onLeft) {
-        if (iconBounds == null) {
-            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
-            return;
-        }
-        canvas.save();
-        // We draw the badge relative to its center.
-        int x = onLeft ? iconBounds.left : iconBounds.right;
-        float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
-        float badgeCenterX = x + offset;
-        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
-
-        canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
-
-        canvas.scale(badgeScale, badgeScale);
-        mCirclePaint.setColor(color);
-        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
-        canvas.restore();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index 783780f..c0053d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -18,12 +18,13 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
-import android.graphics.Point;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.widget.ImageView;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.systemui.R;
 
 /**
@@ -31,16 +32,19 @@
  */
 public class BadgedImageView extends ImageView {
 
-    private BadgeRenderer mDotRenderer;
-    private int mIconSize;
     private Rect mTempBounds = new Rect();
-    private Point mTempPoint = new Point();
 
+    private DotRenderer mDotRenderer;
+    private DotRenderer.DrawParams mDrawParams;
+    private int mIconBitmapSize;
+    private int mDotColor;
     private float mDotScale = 0f;
-    private int mUpdateDotColor;
-    private boolean mShowUpdateDot;
+    private boolean mShowDot;
     private boolean mOnLeft;
 
+    /** Same as value in Launcher3 IconShape */
+    static final int DEFAULT_PATH_SIZE = 100;
+
     public BadgedImageView(Context context) {
         this(context, null);
     }
@@ -56,69 +60,100 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        mIconSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mDotRenderer = new BadgeRenderer(getContext());
+        mIconBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mDrawParams = new DotRenderer.DrawParams();
 
         TypedArray ta = context.obtainStyledAttributes(
-                new int[] {android.R.attr.colorBackgroundFloating});
+                new int[]{android.R.attr.colorBackgroundFloating});
         ta.recycle();
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
-        if (mShowUpdateDot) {
-            getDrawingRect(mTempBounds);
-            mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
-            mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
-                    mOnLeft);
+        if (!mShowDot) {
+            return;
         }
+        getDrawingRect(mTempBounds);
+
+        mDrawParams.color = mDotColor;
+        mDrawParams.iconBounds = mTempBounds;
+        mDrawParams.leftAlign = mOnLeft;
+        mDrawParams.scale = mDotScale;
+
+        if (mDotRenderer == null) {
+            Path circlePath = new Path();
+            float radius = DEFAULT_PATH_SIZE * 0.5f;
+            circlePath.addCircle(radius /* x */, radius /* y */, radius, Path.Direction.CW);
+            mDotRenderer = new DotRenderer(mIconBitmapSize, circlePath, DEFAULT_PATH_SIZE);
+        }
+        mDotRenderer.draw(canvas, mDrawParams);
     }
 
     /**
      * Set whether the dot should appear on left or right side of the view.
      */
-    public void setDotPosition(boolean onLeft) {
+    void setDotOnLeft(boolean onLeft) {
         mOnLeft = onLeft;
         invalidate();
     }
 
-    public boolean getDotPosition() {
+    boolean getDotOnLeft() {
         return mOnLeft;
     }
 
     /**
      * Set whether the dot should show or not.
      */
-    public void setShowDot(boolean showBadge) {
-        mShowUpdateDot = showBadge;
+    void setShowDot(boolean showDot) {
+        mShowDot = showDot;
         invalidate();
     }
 
     /**
      * @return whether the dot is being displayed.
      */
-    public boolean isShowingDot() {
-        return mShowUpdateDot;
+    boolean isShowingDot() {
+        return mShowDot;
     }
 
     /**
      * The colour to use for the dot.
      */
     public void setDotColor(int color) {
-        mUpdateDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        mDotColor = ColorUtils.setAlphaComponent(color, 255 /* alpha */);
+        invalidate();
+    }
+
+    /**
+     * @param iconPath The new icon path to use when calculating dot position.
+     */
+    public void drawDot(Path iconPath) {
+        mDotRenderer = new DotRenderer(mIconBitmapSize, iconPath, DEFAULT_PATH_SIZE);
         invalidate();
     }
 
     /**
      * How big the dot should be, fraction from 0 to 1.
      */
-    public void setDotScale(float fraction) {
+    void setDotScale(float fraction) {
         mDotScale = fraction;
         invalidate();
     }
 
-    public float getDotScale() {
-        return mDotScale;
+    /**
+     * Return dot position relative to bubble view container bounds.
+     */
+    float[] getDotCenter() {
+        float[] dotPosition;
+        if (mOnLeft) {
+            dotPosition = mDotRenderer.getLeftDotPosition();
+        } else {
+            dotPosition =  mDotRenderer.getRightDotPosition();
+        }
+        getDrawingRect(mTempBounds);
+        float dotCenterX = mTempBounds.width() * dotPosition[0];
+        float dotCenterY = mTempBounds.height() * dotPosition[1];
+        return new float[]{dotCenterX, dotCenterY};
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 5c6c397..c3cee35 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -20,38 +20,67 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.LayoutInflater;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
 import java.util.Objects;
 
 /**
  * Encapsulates the data and UI elements of a bubble.
  */
 class Bubble {
-
-    private static final boolean DEBUG = false;
     private static final String TAG = "Bubble";
 
+    private NotificationEntry mEntry;
     private final String mKey;
     private final String mGroupId;
     private String mAppName;
-    private final BubbleExpandedView.OnBubbleBlockedListener mListener;
+    private Drawable mUserBadgedAppIcon;
 
     private boolean mInflated;
-    public NotificationEntry entry;
-    BubbleView iconView;
-    BubbleExpandedView expandedView;
+    private BubbleView mIconView;
+    private BubbleExpandedView mExpandedView;
+
     private long mLastUpdated;
     private long mLastAccessed;
-    private PackageManager mPm;
+    private boolean mIsRemoved;
+
+    /**
+     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+     *
+     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+     * expanded</p>
+     */
+    private boolean mShowInShadeWhenBubble = true;
+
+    /**
+     * Whether the bubble should show a dot for the notification indicating updated content.
+     */
+    private boolean mShowBubbleUpdateDot = true;
+
+    /** Whether flyout text should be suppressed, regardless of any other flags or state. */
+    private boolean mSuppressFlyout;
 
     public static String groupId(NotificationEntry entry) {
         UserHandle user = entry.notification.getUser();
@@ -61,31 +90,27 @@
     /** Used in tests when no UI is required. */
     @VisibleForTesting(visibility = PRIVATE)
     Bubble(Context context, NotificationEntry e) {
-        this (context, e, null);
-    }
-
-    Bubble(Context context, NotificationEntry e,
-            BubbleExpandedView.OnBubbleBlockedListener listener) {
-        entry = e;
+        mEntry = e;
         mKey = e.key;
         mLastUpdated = e.notification.getPostTime();
         mGroupId = groupId(e);
-        mListener = listener;
 
-        mPm = context.getPackageManager();
+        PackageManager pm = context.getPackageManager();
         ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                entry.notification.getPackageName(),
+            info = pm.getApplicationInfo(
+                mEntry.notification.getPackageName(),
                 PackageManager.MATCH_UNINSTALLED_PACKAGES
                     | PackageManager.MATCH_DISABLED_COMPONENTS
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
-                mAppName = String.valueOf(mPm.getApplicationLabel(info));
+                mAppName = String.valueOf(pm.getApplicationLabel(info));
             }
+            Drawable appIcon = pm.getApplicationIcon(mEntry.notification.getPackageName());
+            mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.notification.getUser());
         } catch (PackageManager.NameNotFoundException unused) {
-            mAppName = entry.notification.getPackageName();
+            mAppName = mEntry.notification.getPackageName();
         }
     }
 
@@ -93,12 +118,16 @@
         return mKey;
     }
 
+    public NotificationEntry getEntry() {
+        return mEntry;
+    }
+
     public String getGroupId() {
         return mGroupId;
     }
 
     public String getPackageName() {
-        return entry.notification.getPackageName();
+        return mEntry.notification.getPackageName();
     }
 
     public String getAppName() {
@@ -109,9 +138,23 @@
         return mInflated;
     }
 
-    public void updateDotVisibility() {
-        if (iconView != null) {
-            iconView.updateDotVisibility(true /* animate */);
+    void updateDotVisibility() {
+        if (mIconView != null) {
+            mIconView.updateDotVisibility(true /* animate */);
+        }
+    }
+
+    BubbleView getIconView() {
+        return mIconView;
+    }
+
+    BubbleExpandedView getExpandedView() {
+        return mExpandedView;
+    }
+
+    void cleanupExpandedState() {
+        if (mExpandedView != null) {
+            mExpandedView.cleanUpExpandedState();
         }
     }
 
@@ -119,14 +162,14 @@
         if (mInflated) {
             return;
         }
-        iconView = (BubbleView) inflater.inflate(
+        mIconView = (BubbleView) inflater.inflate(
                 R.layout.bubble_view, stackView, false /* attachToRoot */);
-        iconView.setNotif(entry);
+        mIconView.setBubble(this);
+        mIconView.setAppIcon(mUserBadgedAppIcon);
 
-        expandedView = (BubbleExpandedView) inflater.inflate(
+        mExpandedView = (BubbleExpandedView) inflater.inflate(
                 R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
-        expandedView.setEntry(entry, stackView, mAppName);
-        expandedView.setOnBlockedListener(mListener);
+        mExpandedView.setBubble(this, stackView, mAppName);
 
         mInflated = true;
     }
@@ -140,46 +183,38 @@
      * and setting {@code false} actually means rendering the expanded view in transparent.
      */
     void setContentVisibility(boolean visibility) {
-        if (expandedView != null) {
-            expandedView.setContentVisibility(visibility);
+        if (mExpandedView != null) {
+            mExpandedView.setContentVisibility(visibility);
         }
     }
 
-    void setDismissed() {
-        entry.setBubbleDismissed(true);
-        // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker
-        if (expandedView != null) {
-            expandedView.cleanUpExpandedState();
-        }
-    }
-
-    void setEntry(NotificationEntry entry) {
-        this.entry = entry;
+    void updateEntry(NotificationEntry entry) {
+        mEntry = entry;
         mLastUpdated = entry.notification.getPostTime();
         if (mInflated) {
-            iconView.update(entry);
-            expandedView.update(entry);
+            mIconView.update(this);
+            mExpandedView.update(this);
         }
     }
 
     /**
      * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
      */
-    public long getLastActivity() {
+    long getLastActivity() {
         return Math.max(mLastUpdated, mLastAccessed);
     }
 
     /**
      * @return the timestamp in milliseconds of the most recent notification entry for this bubble
      */
-    public long getLastUpdateTime() {
+    long getLastUpdateTime() {
         return mLastUpdated;
     }
 
     /**
      * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
      */
-    public long getLastAccessTime() {
+    long getLastAccessTime() {
         return mLastAccessed;
     }
 
@@ -187,7 +222,7 @@
      * @return the display id of the virtual display on which bubble contents is drawn.
      */
     int getDisplayId() {
-        return expandedView != null ? expandedView.getVirtualDisplayId() : INVALID_DISPLAY;
+        return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
     }
 
     /**
@@ -195,14 +230,204 @@
      */
     void markAsAccessedAt(long lastAccessedMillis) {
         mLastAccessed = lastAccessedMillis;
-        entry.setShowInShadeWhenBubble(false);
+        setShowInShadeWhenBubble(false);
+        setShowBubbleDot(false);
     }
 
     /**
-     * @return whether bubble is from a notification associated with a foreground service.
+     * Whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
      */
-    public boolean isOngoing() {
-        return entry.isForegroundService();
+    boolean showInShadeWhenBubble() {
+        return !mEntry.isRowDismissed() && !shouldSuppressNotification()
+                && (!mEntry.isClearable() || mShowInShadeWhenBubble);
+    }
+
+    /**
+     * Sets whether this notification should be shown in the shade when it is also displayed as a
+     * bubble.
+     */
+    void setShowInShadeWhenBubble(boolean showInShade) {
+        mShowInShadeWhenBubble = showInShade;
+    }
+
+    /**
+     * Sets whether the bubble for this notification should show a dot indicating updated content.
+     */
+    void setShowBubbleDot(boolean showDot) {
+        mShowBubbleUpdateDot = showDot;
+    }
+
+    /**
+     * Whether the bubble for this notification should show a dot indicating updated content.
+     */
+    boolean showBubbleDot() {
+        return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot();
+    }
+
+    /**
+     * Whether the flyout for the bubble should be shown.
+     */
+    boolean showFlyoutForBubble() {
+        return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
+                && !mEntry.shouldSuppressNotificationList();
+    }
+
+    /**
+     * Set whether the flyout text for the bubble should be shown when an update is received.
+     *
+     * @param suppressFlyout whether the flyout text is shown
+     */
+    void setSuppressFlyout(boolean suppressFlyout) {
+        mSuppressFlyout = suppressFlyout;
+    }
+
+    /**
+     * Returns whether the notification for this bubble is a foreground service. It shows that this
+     * is an ongoing bubble.
+     */
+    boolean isOngoing() {
+        int flags = mEntry.notification.getNotification().flags;
+        return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
+    }
+
+    float getDesiredHeight(Context context) {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return getDimenForPackageUser(context, data.getDesiredHeightResId(),
+                    mEntry.notification.getPackageName(),
+                    mEntry.notification.getUser().getIdentifier());
+        } else {
+            return data.getDesiredHeight()
+                    * context.getResources().getDisplayMetrics().density;
+        }
+    }
+
+    String getDesiredHeightString() {
+        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
+        boolean useRes = data.getDesiredHeightResId() != 0;
+        if (useRes) {
+            return String.valueOf(data.getDesiredHeightResId());
+        } else {
+            return String.valueOf(data.getDesiredHeight());
+        }
+    }
+
+    @Nullable
+    PendingIntent getBubbleIntent(Context context) {
+        Notification notif = mEntry.notification.getNotification();
+        Notification.BubbleMetadata data = notif.getBubbleMetadata();
+        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
+            return data.getIntent();
+        }
+        return null;
+    }
+
+    Intent getSettingsIntent() {
+        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
+        intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
+        intent.putExtra(Settings.EXTRA_APP_UID, mEntry.notification.getUid());
+        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        return intent;
+    }
+
+    /**
+     * Returns our best guess for the most relevant text summary of the latest update to this
+     * notification, based on its type. Returns null if there should not be an update message.
+     */
+    CharSequence getUpdateMessage(Context context) {
+        final Notification underlyingNotif = mEntry.notification.getNotification();
+        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+        try {
+            if (Notification.BigTextStyle.class.equals(style)) {
+                // Return the big text, it is big so probably important. If it's not there use the
+                // normal text.
+                CharSequence bigText =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+                return !TextUtils.isEmpty(bigText)
+                        ? bigText
+                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            } else if (Notification.MessagingStyle.class.equals(style)) {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) underlyingNotif.extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+
+                if (latestMessage != null) {
+                    final CharSequence personName = latestMessage.getSenderPerson() != null
+                            ? latestMessage.getSenderPerson().getName()
+                            : null;
+
+                    // Prepend the sender name if available since group chats also use messaging
+                    // style.
+                    if (!TextUtils.isEmpty(personName)) {
+                        return context.getResources().getString(
+                                R.string.notification_summary_message_format,
+                                personName,
+                                latestMessage.getText());
+                    } else {
+                        return latestMessage.getText();
+                    }
+                }
+            } else if (Notification.InboxStyle.class.equals(style)) {
+                CharSequence[] lines =
+                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+                // Return the last line since it should be the most recent.
+                if (lines != null && lines.length > 0) {
+                    return lines[lines.length - 1];
+                }
+            } else if (Notification.MediaStyle.class.equals(style)) {
+                // Return nothing, media updates aren't typically useful as a text update.
+                return null;
+            } else {
+                // Default to text extra.
+                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            }
+        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // No use crashing, we'll just return null and the caller will assume there's no update
+            // message.
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
+        PackageManager pm = context.getPackageManager();
+        Resources r;
+        if (pkg != null) {
+            try {
+                if (userId == UserHandle.USER_ALL) {
+                    userId = UserHandle.USER_SYSTEM;
+                }
+                r = pm.getResourcesForApplicationAsUser(pkg, userId);
+                return r.getDimensionPixelSize(resId);
+            } catch (PackageManager.NameNotFoundException ex) {
+                // Uninstalled, don't care
+            } catch (Resources.NotFoundException e) {
+                // Invalid res id, return 0 and user our default
+                Log.e(TAG, "Couldn't find desired height res id", e);
+            }
+        }
+        return 0;
+    }
+
+    private boolean shouldSuppressNotification() {
+        return mEntry.getBubbleMetadata() != null
+                && mEntry.getBubbleMetadata().isNotificationSuppressed();
+    }
+
+    boolean shouldAutoExpand() {
+        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
+        return metadata != null && metadata.getAutoExpandBubble();
     }
 
     @Override
@@ -210,6 +435,20 @@
         return "Bubble{" + mKey + '}';
     }
 
+    /**
+     * Description of current bubble state.
+     */
+    public void dump(
+            @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.print("key: "); pw.println(mKey);
+        pw.print("  showInShade:   "); pw.println(showInShadeWhenBubble());
+        pw.print("  showDot:       "); pw.println(showBubbleDot());
+        pw.print("  showFlyout:    "); pw.println(showFlyoutForBubble());
+        pw.print("  desiredHeight: "); pw.println(getDesiredHeightString());
+        pw.print("  suppressNotif: "); pw.println(shouldSuppressNotification());
+        pw.print("  autoExpand:    "); pw.println(shouldAutoExpand());
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index a23c99e..72ada6e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -16,21 +16,24 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
 
@@ -39,27 +42,23 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.app.ActivityManager;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseSetArray;
 import android.view.Display;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
@@ -73,20 +72,26 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -101,12 +106,12 @@
 @Singleton
 public class BubbleController implements ConfigurationController.ConfigurationListener {
 
-    private static final String TAG = "BubbleController";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
 
     @Retention(SOURCE)
     @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
-            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
+            DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
+            DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT})
     @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
     @interface DismissReason {}
 
@@ -117,35 +122,28 @@
     static final int DISMISS_NOTIF_CANCEL = 5;
     static final int DISMISS_ACCESSIBILITY_ACTION = 6;
     static final int DISMISS_NO_LONGER_BUBBLE = 7;
+    static final int DISMISS_USER_CHANGED = 8;
+    static final int DISMISS_GROUP_CANCELLED = 9;
+    static final int DISMISS_INVALID_INTENT = 10;
 
     public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
 
-    // Enables some subset of notifs to automatically become bubbles
-    public static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
-
-    /** Flag to enable or disable the entire feature */
-    private static final String ENABLE_BUBBLES = "experiment_enable_bubbles";
-    /** Auto bubble flags set whether different notif types should be presented as a bubble */
-    private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
-    private static final String ENABLE_AUTO_BUBBLE_ONGOING = "experiment_autobubble_ongoing";
-    private static final String ENABLE_AUTO_BUBBLE_ALL = "experiment_autobubble_all";
-
-    /** Use an activityView for an auto-bubbled notifs if it has an appropriate content intent */
-    private static final String ENABLE_BUBBLE_CONTENT_INTENT = "experiment_bubble_content_intent";
-
-    private static final String BUBBLE_STIFFNESS = "experiment_bubble_stiffness";
-    private static final String BUBBLE_BOUNCINESS = "experiment_bubble_bounciness";
-
     private final Context mContext;
     private final NotificationEntryManager mNotificationEntryManager;
     private final BubbleTaskStackListener mTaskStackListener;
     private BubbleStateChangeListener mStateChangeListener;
     private BubbleExpandListener mExpandListener;
     @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
+    private final NotificationGroupManager mNotificationGroupManager;
 
     private BubbleData mBubbleData;
     @Nullable private BubbleStackView mStackView;
 
+    // Tracks the id of the current (foreground) user.
+    private int mCurrentUserId;
+    // Saves notification keys of active bubbles when users are switched.
+    private final SparseSetArray<String> mSavedBubbleKeysPerUser;
+
     // Bubbles get added to the status bar view
     private final StatusBarWindowController mStatusBarWindowController;
     private final ZenModeController mZenModeController;
@@ -157,6 +155,9 @@
     // Used for determining view rect for touch interaction
     private Rect mTempRect = new Rect();
 
+    // Listens to user switch so bubbles can be saved and restored.
+    private final NotificationLockscreenUserManager mNotifUserManager;
+
     /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -211,28 +212,38 @@
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         this(context, statusBarWindowController, data, null /* synchronizer */,
-                configurationController, interruptionStateProvider, zenModeController);
+                configurationController, interruptionStateProvider, zenModeController,
+                notifUserManager, groupManager);
     }
 
     public BubbleController(Context context, StatusBarWindowController statusBarWindowController,
             BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
             ConfigurationController configurationController,
             NotificationInterruptionStateProvider interruptionStateProvider,
-            ZenModeController zenModeController) {
+            ZenModeController zenModeController,
+            NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManager groupManager) {
         mContext = context;
         mNotificationInterruptionStateProvider = interruptionStateProvider;
+        mNotifUserManager = notifUserManager;
         mZenModeController = zenModeController;
         mZenModeController.addCallback(new ZenModeController.Callback() {
             @Override
             public void onZenChanged(int zen) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
 
             @Override
             public void onConfigChanged(ZenModeConfig config) {
-                updateStackViewForZenConfig();
+                if (mStackView != null) {
+                    mStackView.updateDots();
+                }
             }
         });
 
@@ -244,6 +255,24 @@
         mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
         mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
         mNotificationEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
+        mNotificationGroupManager = groupManager;
+        mNotificationGroupManager.addOnGroupChangeListener(
+                new NotificationGroupManager.OnGroupChangeListener() {
+                    @Override
+                    public void onGroupSuppressionChanged(
+                            NotificationGroupManager.NotificationGroup group,
+                            boolean suppressed) {
+                        // More notifications could be added causing summary to no longer
+                        // be suppressed -- in this case need to remove the key.
+                        final String groupKey = group.summary != null
+                                ? group.summary.notification.getGroupKey()
+                                : null;
+                        if (!suppressed && groupKey != null
+                                && mBubbleData.isSummarySuppressed(groupKey)) {
+                            mBubbleData.removeSuppressedSummary(groupKey);
+                        }
+                    }
+                });
 
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarStateListener = new StatusBarStateListener();
@@ -261,6 +290,16 @@
 
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+        mSavedBubbleKeysPerUser = new SparseSetArray<>();
+        mCurrentUserId = mNotifUserManager.getCurrentUserId();
+        mNotifUserManager.addUserChangedListener(
+                newUserId -> {
+                    saveBubbles(mCurrentUserId);
+                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+                    restoreBubbles(newUserId);
+                    mCurrentUserId = newUserId;
+                });
     }
 
     /**
@@ -271,19 +310,55 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
             ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
-            // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
-            //  scrim between bubble and the shade
-            int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
-            sbv.addView(mStackView, bubblePosition,
+            int bubbleScrimIndex = sbv.indexOfChild(sbv.findViewById(R.id.scrim_for_bubble));
+            int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
+            sbv.addView(mStackView, stackIndex,
                     new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
-
-            updateStackViewForZenConfig();
         }
     }
 
+    /**
+     * Records the notification key for any active bubbles. These are used to restore active
+     * bubbles when the user returns to the foreground.
+     *
+     * @param userId the id of the user
+     */
+    private void saveBubbles(@UserIdInt int userId) {
+        // First clear any existing keys that might be stored.
+        mSavedBubbleKeysPerUser.remove(userId);
+        // Add in all active bubbles for the current user.
+        for (Bubble bubble: mBubbleData.getBubbles()) {
+            mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
+        }
+    }
+
+    /**
+     * Promotes existing notifications to Bubbles if they were previously bubbles.
+     *
+     * @param userId the id of the user
+     */
+    private void restoreBubbles(@UserIdInt int userId) {
+        NotificationData notificationData =
+                mNotificationEntryManager.getNotificationData();
+        ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
+        if (savedBubbleKeys == null) {
+            // There were no bubbles saved for this used.
+            return;
+        }
+        for (NotificationEntry e : notificationData.getNotificationsForCurrentUser()) {
+            if (savedBubbleKeys.contains(e.key)
+                    && mNotificationInterruptionStateProvider.shouldBubbleUp(e)
+                    && canLaunchInActivityView(mContext, e)) {
+                updateBubble(e, /* suppressFlyout= */ true);
+            }
+        }
+        // Finally, remove the entries for this user now that bubbles are restored.
+        mSavedBubbleKeysPerUser.remove(mCurrentUserId);
+    }
+
     @Override
     public void onUiModeChanged() {
         if (mStackView != null) {
@@ -301,8 +376,8 @@
     @Override
     public void onConfigChanged(Configuration newConfig) {
         if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
-            mStackView.onOrientationChanged();
             mOrientation = newConfig.orientation;
+            mStackView.onOrientationChanged(newConfig.orientation);
         }
     }
 
@@ -360,6 +435,25 @@
         mBubbleData.setExpanded(false /* expanded */);
     }
 
+    /**
+     * True if either:
+     * (1) There is a bubble associated with the provided key and if its notification is hidden
+     *     from the shade.
+     * (2) There is a group summary associated with the provided key that is hidden from the shade
+     *     because it has been dismissed but still has child bubbles active.
+     *
+     * False otherwise.
+     */
+    public boolean isBubbleNotificationSuppressedFromShade(String key) {
+        boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
+                && !mBubbleData.getBubbleWithKey(key).showInShadeWhenBubble();
+        NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+        String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+        boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
+        boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
+        return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
+    }
+
     void selectBubble(Bubble bubble) {
         mBubbleData.setSelectedBubble(bubble);
     }
@@ -406,11 +500,15 @@
      * @param notif the notification associated with this bubble.
      */
     void updateBubble(NotificationEntry notif) {
+        updateBubble(notif, /* supressFlyout */ false);
+    }
+
+    void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
         // If this is an interruptive notif, mark that it's interrupted
-        if (notif.importance >= NotificationManager.IMPORTANCE_HIGH) {
+        if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
             notif.setInterruption();
         }
-        mBubbleData.notificationEntryUpdated(notif);
+        mBubbleData.notificationEntryUpdated(notif, suppressFlyout);
     }
 
     /**
@@ -423,7 +521,7 @@
         // TEMP: refactor to change this to pass entry
         Bubble bubble = mBubbleData.getBubbleWithKey(key);
         if (bubble != null) {
-            mBubbleData.notificationEntryRemoved(bubble.entry, reason);
+            mBubbleData.notificationEntryRemoved(bubble.getEntry(), reason);
         }
     }
 
@@ -432,33 +530,50 @@
             new NotificationRemoveInterceptor() {
             @Override
             public boolean onNotificationRemoveRequested(String key, int reason) {
-                if (!mBubbleData.hasBubbleWithKey(key)) {
+                NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+                String groupKey = entry != null ? entry.notification.getGroupKey() : null;
+                ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+                boolean inBubbleData = mBubbleData.hasBubbleWithKey(key);
+                boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
+                        && mBubbleData.getSummaryKey(groupKey).equals(key));
+                boolean isSummary = entry != null
+                        && entry.notification.getNotification().isGroupSummary();
+                boolean isSummaryOfBubbles = (isSuppressedSummary || isSummary)
+                        && bubbleChildren != null && !bubbleChildren.isEmpty();
+
+                if (!inBubbleData && !isSummaryOfBubbles) {
                     return false;
                 }
-                NotificationEntry entry = mBubbleData.getBubbleWithKey(key).entry;
 
                 final boolean isClearAll = reason == REASON_CANCEL_ALL;
-                final boolean isUserDimiss = reason == REASON_CANCEL;
+                final boolean isUserDimiss = reason == REASON_CANCEL || reason == REASON_CLICK;
                 final boolean isAppCancel = reason == REASON_APP_CANCEL
                         || reason == REASON_APP_CANCEL_ALL;
+                final boolean isSummaryCancel = reason == REASON_GROUP_SUMMARY_CANCELED;
 
                 // Need to check for !appCancel here because the notification may have
                 // previously been dismissed & entry.isRowDismissed would still be true
-                boolean userRemovedNotif = (entry.isRowDismissed() && !isAppCancel)
-                        || isClearAll || isUserDimiss;
+                boolean userRemovedNotif = (entry != null && entry.isRowDismissed() && !isAppCancel)
+                        || isClearAll || isUserDimiss || isSummaryCancel;
+
+                if (isSummaryOfBubbles) {
+                    return handleSummaryRemovalInterception(entry, userRemovedNotif);
+                }
 
                 // The bubble notification sticks around in the data as long as the bubble is
                 // not dismissed and the app hasn't cancelled the notification.
-                boolean bubbleExtended = entry.isBubble() && !entry.isBubbleDismissed()
-                        && userRemovedNotif;
+                Bubble bubble = mBubbleData.getBubbleWithKey(key);
+                boolean bubbleExtended = entry != null && entry.isBubble() && userRemovedNotif;
                 if (bubbleExtended) {
-                    entry.setShowInShadeWhenBubble(false);
+                    bubble.setShowInShadeWhenBubble(false);
+                    bubble.setShowBubbleDot(false);
                     if (mStackView != null) {
                         mStackView.updateDotVisibility(entry.key);
                     }
                     mNotificationEntryManager.updateNotifications();
                     return true;
-                } else if (!userRemovedNotif && !entry.isBubbleDismissed()) {
+                } else if (!userRemovedNotif && entry != null) {
                     // This wasn't a user removal so we should remove the bubble as well
                     mBubbleData.notificationEntryRemoved(entry, DISMISS_NOTIF_CANCEL);
                     return false;
@@ -467,24 +582,56 @@
             }
         };
 
+    private boolean handleSummaryRemovalInterception(NotificationEntry summary,
+            boolean userRemovedNotif) {
+        String groupKey = summary.notification.getGroupKey();
+        ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
+
+        if (userRemovedNotif) {
+            // If it's a user dismiss we mark the children to be hidden from the shade.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                // As far as group manager is concerned, once a child is no longer shown
+                // in the shade, it is essentially removed.
+                mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
+                bubbleChild.setShowInShadeWhenBubble(false);
+                bubbleChild.setShowBubbleDot(false);
+                if (mStackView != null) {
+                    mStackView.updateDotVisibility(bubbleChild.getKey());
+                }
+            }
+            // And since all children are removed, remove the summary.
+            mNotificationGroupManager.onEntryRemoved(summary);
+
+            // If the summary was auto-generated we don't need to keep that notification around
+            // because apps can't cancel it; so we only intercept & suppress real summaries.
+            boolean isAutogroupSummary = (summary.notification.getNotification().flags
+                    & FLAG_AUTOGROUP_SUMMARY) != 0;
+            if (!isAutogroupSummary) {
+                mBubbleData.addSummaryToSuppress(summary.notification.getGroupKey(),
+                        summary.key);
+                // Tell shade to update for the suppression
+                mNotificationEntryManager.updateNotifications();
+            }
+            return !isAutogroupSummary;
+        } else {
+            // If it's not a user dismiss it's a cancel.
+            mBubbleData.removeSuppressedSummary(groupKey);
+
+            // Remove any associated bubble children.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                Bubble bubbleChild = bubbleChildren.get(i);
+                mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
+                        DISMISS_GROUP_CANCELLED);
+            }
+            return false;
+        }
+    }
+
     @SuppressWarnings("FieldCanBeLocal")
     private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
-            if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
-                    && canLaunchInActivityView(mContext, entry)) {
-                updateShowInShadeForSuppressNotification(entry);
-            }
-        }
-
-        @Override
-        public void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry)) {
                 updateBubble(entry);
@@ -493,17 +640,13 @@
 
         @Override
         public void onPreEntryUpdated(NotificationEntry entry) {
-            if (!areBubblesEnabled(mContext)) {
-                return;
-            }
             boolean shouldBubble = mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
                     && canLaunchInActivityView(mContext, entry);
             if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.key)) {
                 // It was previously a bubble but no longer a bubble -- lets remove it
                 removeBubble(entry.key, DISMISS_NO_LONGER_BUBBLE);
             } else if (shouldBubble) {
-                updateShowInShadeForSuppressNotification(entry);
-                entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+                Bubble b = mBubbleData.getBubbleWithKey(entry.key);
                 updateBubble(entry);
             }
         }
@@ -540,27 +683,60 @@
             }
 
             // Do removals, if any.
-            for (Pair<Bubble, Integer> removed : update.removedBubbles) {
+            ArrayList<Pair<Bubble, Integer>> removedBubbles =
+                    new ArrayList<>(update.removedBubbles);
+            for (Pair<Bubble, Integer> removed : removedBubbles) {
                 final Bubble bubble = removed.first;
                 @DismissReason final int reason = removed.second;
                 mStackView.removeBubble(bubble);
 
-                if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
-                        && !bubble.entry.showInShadeWhenBubble()) {
-                    // The bubble is gone & the notification is gone, time to actually remove it
-                    mNotificationEntryManager.performRemoveNotification(bubble.entry.notification,
-                            UNDEFINED_DISMISS_REASON);
-                } else {
-                    // Update the flag for SysUI
-                    bubble.entry.notification.getNotification().flags &= ~FLAG_BUBBLE;
+                // If the bubble is removed for user switching, leave the notification in place.
+                if (reason != DISMISS_USER_CHANGED) {
+                    if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
+                            && !bubble.showInShadeWhenBubble()) {
+                        // The bubble is gone & the notification is gone, time to actually remove it
+                        mNotificationEntryManager.performRemoveNotification(
+                                bubble.getEntry().notification, UNDEFINED_DISMISS_REASON);
+                    } else {
+                        // Update the flag for SysUI
+                        bubble.getEntry().notification.getNotification().flags &= ~FLAG_BUBBLE;
 
-                    // Make sure NoMan knows it's not a bubble anymore so anyone querying it will
-                    // get right result back
-                    try {
-                        mBarService.onNotificationBubbleChanged(bubble.getKey(),
-                                false /* isBubble */);
-                    } catch (RemoteException e) {
-                        // Bad things have happened
+                        // Make sure NoMan knows it's not a bubble anymore so anyone querying it
+                        // will get right result back
+                        try {
+                            mBarService.onNotificationBubbleChanged(bubble.getKey(),
+                                    false /* isBubble */);
+                        } catch (RemoteException e) {
+                            // Bad things have happened
+                        }
+                    }
+
+                    // Check if removed bubble has an associated suppressed group summary that needs
+                    // to be removed now.
+                    final String groupKey = bubble.getEntry().notification.getGroupKey();
+                    if (mBubbleData.isSummarySuppressed(groupKey)
+                            && mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+                        // Time to actually remove the summary.
+                        String notifKey = mBubbleData.getSummaryKey(groupKey);
+                        mBubbleData.removeSuppressedSummary(groupKey);
+                        NotificationEntry entry =
+                                mNotificationEntryManager.getNotificationData().get(notifKey);
+                        mNotificationEntryManager.performRemoveNotification(
+                                entry.notification, UNDEFINED_DISMISS_REASON);
+                    }
+
+                    // Check if summary should be removed from NoManGroup
+                    NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(
+                            bubble.getEntry().notification);
+                    if (summary != null) {
+                        ArrayList<NotificationEntry> summaryChildren =
+                                mNotificationGroupManager.getLogicalChildren(summary.notification);
+                        boolean isSummaryThisNotif = summary.key.equals(bubble.getEntry().key);
+                        if (!isSummaryThisNotif
+                                && (summaryChildren == null || summaryChildren.isEmpty())) {
+                            mNotificationEntryManager.performRemoveNotification(
+                                    summary.notification, UNDEFINED_DISMISS_REASON);
+                        }
                     }
                 }
             }
@@ -575,6 +751,10 @@
 
             if (update.selectionChanged) {
                 mStackView.setSelectedBubble(update.selectedBubble);
+                if (update.selectedBubble != null) {
+                    mNotificationGroupManager.updateSuppression(
+                            update.selectedBubble.getEntry());
+                }
             }
 
             // Expanding? Apply this last.
@@ -585,7 +765,7 @@
             mNotificationEntryManager.updateNotifications();
             updateStack();
 
-            if (DEBUG) {
+            if (DEBUG_BUBBLE_CONTROLLER) {
                 Log.d(TAG, "[BubbleData]");
                 Log.d(TAG, formatBubblesString(mBubbleData.getBubbles(),
                         mBubbleData.getSelectedBubble()));
@@ -600,34 +780,6 @@
     };
 
     /**
-     * Updates the stack view's suppression flags from the latest config from the zen (do not
-     * disturb) controller.
-     */
-    private void updateStackViewForZenConfig() {
-        final ZenModeConfig zenModeConfig = mZenModeController.getConfig();
-
-        if (zenModeConfig == null || mStackView == null) {
-            return;
-        }
-
-        final int suppressedEffects = zenModeConfig.suppressedVisualEffects;
-        final boolean hideNotificationDotsSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_BADGE) != 0;
-        final boolean dontPopNotifsOnScreenSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_PEEK) != 0;
-        final boolean hideFromPullDownShadeSelected =
-                (suppressedEffects & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
-
-        final boolean dndEnabled = mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF;
-
-        mStackView.setSuppressNewDot(
-                dndEnabled && hideNotificationDotsSelected);
-        mStackView.setSuppressFlyout(
-                dndEnabled && (dontPopNotifsOnScreenSelected
-                        || hideFromPullDownShadeSelected));
-    }
-
-    /**
      * Lets any listeners know if bubble state has changed.
      * Updates the visibility of the bubbles based on current state.
      * Does not un-bubble, just hides or un-hides. Notifies any
@@ -697,46 +849,16 @@
     }
 
     /**
-     * Whether the notification should automatically bubble or not. Gated by secure settings flags.
+     * Description of current bubble state.
      */
-    @VisibleForTesting
-    protected boolean shouldAutoBubbleForFlags(Context context, NotificationEntry entry) {
-        if (entry.isBubbleDismissed()) {
-            return false;
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("BubbleController state:");
+        mBubbleData.dump(fd, pw, args);
+        pw.println();
+        if (mStackView != null) {
+            mStackView.dump(fd, pw, args);
         }
-        StatusBarNotification n = entry.notification;
-
-        boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-        boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
-
-        boolean hasRemoteInput = false;
-        if (n.getNotification().actions != null) {
-            for (Notification.Action action : n.getNotification().actions) {
-                if (action.getRemoteInputs() != null) {
-                    hasRemoteInput = true;
-                    break;
-                }
-            }
-        }
-        boolean isCall = Notification.CATEGORY_CALL.equals(n.getNotification().category)
-                && n.isOngoing();
-        boolean isMusic = n.getNotification().hasMediaSession();
-        boolean isImportantOngoing = isMusic || isCall;
-
-        Class<? extends Notification.Style> style = n.getNotification().getNotificationStyle();
-        boolean isMessageType = Notification.CATEGORY_MESSAGE.equals(n.getNotification().category);
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        return (((isMessageType && hasRemoteInput) || isMessageStyle) && autoBubbleMessages)
-                || (isImportantOngoing && autoBubbleOngoing)
-                || autoBubbleAll;
-    }
-
-    private void updateShowInShadeForSuppressNotification(NotificationEntry entry) {
-        boolean suppressNotification = entry.getBubbleMetadata() != null
-                && entry.getBubbleMetadata().isNotificationSuppressed()
-                && isForegroundApp(mContext, entry.notification.getPackageName());
-        entry.setShowInShadeWhenBubble(!suppressNotification);
+        pw.println();
     }
 
     static String formatBubblesString(List<Bubble> bubbles, Bubble selected) {
@@ -757,18 +879,6 @@
     }
 
     /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param context application context.
-     * @param pkgName application package name.
-     */
-    public static boolean isForegroundApp(Context context, String pkgName) {
-        ActivityManager am = context.getSystemService(ActivityManager.class);
-        List<RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    /**
      * This task stack listener is responsible for responding to tasks moved to the front
      * which are on the default (main) display. When this happens, expanded bubbles must be
      * collapsed so the user may interact with the app which was just moved to the front.
@@ -782,7 +892,9 @@
         @Override
         public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
             if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) {
-                mBubbleData.setExpanded(false);
+                if (!mStackView.isExpansionAnimating()) {
+                    mBubbleData.setExpanded(false);
+                }
             }
         }
 
@@ -802,50 +914,25 @@
 
         @Override
         public void onSingleTaskDisplayDrawn(int displayId) {
-            final Bubble expandedBubble = getExpandedBubble(mContext);
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
             if (expandedBubble != null && expandedBubble.getDisplayId() == displayId) {
                 expandedBubble.setContentVisibility(true);
             }
         }
-    }
 
-    private static boolean shouldAutoBubbleMessages(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_MESSAGES, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleOngoing(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ONGOING, 0) != 0;
-    }
-
-    private static boolean shouldAutoBubbleAll(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_AUTO_BUBBLE_ALL, 0) != 0;
-    }
-
-    static boolean shouldUseContentIntent(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLE_CONTENT_INTENT, 0) != 0;
-    }
-
-    private static boolean areBubblesEnabled(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ENABLE_BUBBLES, 1) != 0;
-    }
-
-    /** Default stiffness to use for bubble physics animations. */
-    public static int getBubbleStiffness(Context context, int defaultStiffness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(), BUBBLE_STIFFNESS, defaultStiffness);
-    }
-
-    /** Default bounciness/damping ratio to use for bubble physics animations. */
-    public static float getBubbleBounciness(Context context, float defaultBounciness) {
-        return Settings.Secure.getInt(
-                context.getContentResolver(),
-                BUBBLE_BOUNCINESS,
-                (int) (defaultBounciness * 100)) / 100f;
+        @Override
+        public void onSingleTaskDisplayEmpty(int displayId) {
+            final Bubble expandedBubble = mStackView != null
+                    ? mStackView.getExpandedBubble()
+                    : null;
+            int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1;
+            if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) {
+                mBubbleData.setExpanded(false);
+            }
+            mBubbleData.notifyDisplayEmpty(displayId);
+        }
     }
 
     /**
@@ -891,32 +978,12 @@
     }
 
     /** PinnedStackListener that dispatches IME visibility updates to the stack. */
-    private class BubblesImeListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) throws RemoteException {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) throws RemoteException {}
-
+    private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mStackView != null && mStackView.getBubbleCount() > 0) {
                 mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
             }
         }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight)
-                throws RemoteException {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) throws RemoteException {}
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) throws RemoteException {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 5575b35..d43e030 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -16,6 +16,9 @@
 package com.android.systemui.bubbles;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import static java.util.stream.Collectors.toList;
 
@@ -33,11 +36,12 @@
 import com.android.systemui.bubbles.BubbleController.DismissReason;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,8 +55,7 @@
 @Singleton
 public class BubbleData {
 
-    private static final String TAG = "BubbleData";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
 
     private static final int MAX_BUBBLES = 5;
 
@@ -123,6 +126,19 @@
     @Nullable
     private Listener mListener;
 
+    /**
+     * We track groups with summaries that aren't visibly displayed but still kept around because
+     * the bubble(s) associated with the summary still exist.
+     *
+     * The summary must be kept around so that developers can cancel it (and hence the bubbles
+     * associated with it). This list is used to check if the summary should be hidden from the
+     * shade.
+     *
+     * Key: group key of the NotificationEntry
+     * Value: key of the NotificationEntry
+     */
+    private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
+
     @Inject
     public BubbleData(Context context) {
         mContext = context;
@@ -148,7 +164,7 @@
     }
 
     public void setExpanded(boolean expanded) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpanded: " + expanded);
         }
         setExpandedInternal(expanded);
@@ -156,29 +172,34 @@
     }
 
     public void setSelectedBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubble: " + bubble);
         }
         setSelectedBubbleInternal(bubble);
         dispatchPendingChanges();
     }
 
-    public void notificationEntryUpdated(NotificationEntry entry) {
-        if (DEBUG) {
+    void notificationEntryUpdated(NotificationEntry entry, boolean suppressFlyout) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + entry);
         }
         Bubble bubble = getBubbleWithKey(entry.key);
+        suppressFlyout = !entry.isVisuallyInterruptive || suppressFlyout;
+
         if (bubble == null) {
             // Create a new bubble
-            bubble = new Bubble(mContext, entry, this::onBubbleBlocked);
+            bubble = new Bubble(mContext, entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doAdd(bubble);
             trim();
         } else {
             // Updates an existing bubble
-            bubble.setEntry(entry);
+            bubble.updateEntry(entry);
+            bubble.setSuppressFlyout(suppressFlyout);
             doUpdate(bubble);
         }
-        if (shouldAutoExpand(entry)) {
+
+        if (bubble.shouldAutoExpand()) {
             setSelectedBubbleInternal(bubble);
             if (!mExpanded) {
                 setExpandedInternal(true);
@@ -186,11 +207,14 @@
         } else if (mSelectedBubble == null) {
             setSelectedBubbleInternal(bubble);
         }
+        boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
+        bubble.setShowInShadeWhenBubble(!isBubbleExpandedAndSelected);
+        bubble.setShowBubbleDot(!isBubbleExpandedAndSelected);
         dispatchPendingChanges();
     }
 
     public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
         }
         doRemove(entry.key, reason);
@@ -222,8 +246,59 @@
         dispatchPendingChanges();
     }
 
+    /**
+     * Adds a group key indicating that the summary for this group should be suppressed.
+     *
+     * @param groupKey the group key of the group whose summary should be suppressed.
+     * @param notifKey the notification entry key of that summary.
+     */
+    void addSummaryToSuppress(String groupKey, String notifKey) {
+        mSuppressedGroupKeys.put(groupKey, notifKey);
+    }
+
+    /**
+     * Retrieves the notif entry key of the summary associated with the provided group key.
+     *
+     * @param groupKey the group to look up
+     * @return the key for the {@link NotificationEntry} that is the summary of this group.
+     */
+    String getSummaryKey(String groupKey) {
+        return mSuppressedGroupKeys.get(groupKey);
+    }
+
+    /**
+     * Removes a group key indicating that summary for this group should no longer be suppressed.
+     */
+    void removeSuppressedSummary(String groupKey) {
+        mSuppressedGroupKeys.remove(groupKey);
+    }
+
+    /**
+     * Whether the summary for the provided group key is suppressed.
+     */
+    boolean isSummarySuppressed(String groupKey) {
+        return mSuppressedGroupKeys.containsKey(groupKey);
+    }
+
+    /**
+     * Retrieves any bubbles that are part of the notification group represented by the provided
+     * group key.
+     */
+    ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
+        ArrayList<Bubble> bubbleChildren = new ArrayList<>();
+        if (groupKey == null) {
+            return bubbleChildren;
+        }
+        for (Bubble b : mBubbles) {
+            if (groupKey.equals(b.getEntry().notification.getGroupKey())) {
+                bubbleChildren.add(b);
+            }
+        }
+        return bubbleChildren;
+    }
+
     private void doAdd(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doAdd: " + bubble);
         }
         int minInsertPoint = 0;
@@ -256,7 +331,7 @@
     }
 
     private void doUpdate(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "doUpdate: " + bubble);
         }
         mStateChange.updatedBubble = bubble;
@@ -301,12 +376,11 @@
             Bubble newSelected = mBubbles.get(newIndex);
             setSelectedBubbleInternal(newSelected);
         }
-        bubbleToRemove.setDismissed();
-        maybeSendDeleteIntent(reason, bubbleToRemove.entry);
+        maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
     }
 
     public void dismissAll(@DismissReason int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "dismissAll: reason=" + reason);
         }
         if (mBubbles.isEmpty()) {
@@ -316,13 +390,28 @@
         setSelectedBubbleInternal(null);
         while (!mBubbles.isEmpty()) {
             Bubble bubble = mBubbles.remove(0);
-            bubble.setDismissed();
-            maybeSendDeleteIntent(reason, bubble.entry);
+            maybeSendDeleteIntent(reason, bubble.getEntry());
             mStateChange.bubbleRemoved(bubble, reason);
         }
         dispatchPendingChanges();
     }
 
+    /**
+     * Indicates that the provided display is no longer in use and should be cleaned up.
+     *
+     * @param displayId the id of the display to clean up.
+     */
+    void notifyDisplayEmpty(int displayId) {
+        for (Bubble b : mBubbles) {
+            if (b.getDisplayId() == displayId) {
+                if (b.getExpandedView() != null) {
+                    b.getExpandedView().notifyDisplayEmpty();
+                }
+                return;
+            }
+        }
+    }
+
     private void dispatchPendingChanges() {
         if (mListener != null && mStateChange.anythingChanged()) {
             mListener.applyUpdate(mStateChange);
@@ -336,7 +425,7 @@
      * @param bubble the new selected bubble
      */
     private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
         }
         if (Objects.equals(bubble, mSelectedBubble)) {
@@ -361,7 +450,7 @@
      * @param shouldExpand the new requested state
      */
     private void setExpandedInternal(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
         }
         if (mExpanded == shouldExpand) {
@@ -396,7 +485,7 @@
                     // bubble remains on top.
                     mBubbles.remove(mSelectedBubble);
                     mBubbles.add(0, mSelectedBubble);
-                    packGroup(0);
+                    mStateChange.orderChanged |= packGroup(0);
                 }
             }
         }
@@ -466,7 +555,7 @@
      * @return true if the position of any bubbles has changed as a result
      */
     private boolean packGroup(int position) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "packGroup: position=" + position);
         }
         Bubble groupStart = mBubbles.get(position);
@@ -495,7 +584,7 @@
      * @return true if the position of any bubbles changed as a result
      */
     private boolean repackAll() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "repackAll()");
         }
         if (mBubbles.isEmpty()) {
@@ -550,28 +639,6 @@
         }
     }
 
-    private void onBubbleBlocked(NotificationEntry entry) {
-        final String blockedGroupId = Bubble.groupId(entry);
-        int selectedIndex = mBubbles.indexOf(mSelectedBubble);
-        for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
-            Bubble bubble = i.next();
-            if (bubble.getGroupId().equals(blockedGroupId)) {
-                mStateChange.bubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
-                i.remove();
-            }
-        }
-        if (mBubbles.isEmpty()) {
-            setExpandedInternal(false);
-            setSelectedBubbleInternal(null);
-        } else if (!mBubbles.contains(mSelectedBubble)) {
-            // choose a new one
-            int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
-            Bubble newSelected = mBubbles.get(newIndex);
-            setSelectedBubbleInternal(newSelected);
-        }
-        dispatchPendingChanges();
-    }
-
     private int indexForKey(String key) {
         for (int i = 0; i < mBubbles.size(); i++) {
             Bubble bubble = mBubbles.get(i);
@@ -610,9 +677,21 @@
         mListener = listener;
     }
 
-    boolean shouldAutoExpand(NotificationEntry entry) {
-        Notification.BubbleMetadata metadata = entry.getBubbleMetadata();
-        return metadata != null && metadata.getAutoExpandBubble()
-                && BubbleController.isForegroundApp(mContext, entry.notification.getPackageName());
+    /**
+     * Description of current bubble data state.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("selected: "); pw.println(mSelectedBubble != null
+                ? mSelectedBubble.getKey()
+                : "null");
+        pw.print("expanded: "); pw.println(mExpanded);
+        pw.print("count:    "); pw.println(mBubbles.size());
+        for (Bubble bubble : mBubbles) {
+            bubble.dump(fd, pw, args);
+        }
+        pw.print("summaryKeys: "); pw.println(mSuppressedGroupKeys.size());
+        for (String key : mSuppressedGroupKeys.keySet()) {
+            pw.println("   suppressing: " + key);
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
new file mode 100644
index 0000000..b702d06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+/**
+ * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
+ * package.
+ */
+public class BubbleDebugConfig {
+
+    // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
+    // log output. This makes it easy to identify the origin of the log message when sifting
+    // through a large amount of log output from multiple sources. However, it also makes trying
+    // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
+    // setting this constant to true, log messages from the Bubbles package will be tagged with
+    // their class names instead fot the generic tag.
+    static final boolean TAG_WITH_CLASS_NAME = false;
+
+    // Default log tag for the Bubbles package.
+    static final String TAG_BUBBLES = "Bubbles";
+
+    static final boolean DEBUG_BUBBLE_CONTROLLER = false;
+    static final boolean DEBUG_BUBBLE_DATA = false;
+    static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
+    static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
index 4db1e27..9db371e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
@@ -16,18 +16,13 @@
 
 package com.android.systemui.bubbles;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringAnimation;
@@ -37,14 +32,13 @@
 
 /** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
 public class BubbleDismissView extends FrameLayout {
-    /** Duration for animations involving the dismiss target text/icon/gradient. */
+    /** Duration for animations involving the dismiss target text/icon. */
     private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
-
-    private View mDismissGradient;
+    private static final float SCALE_FOR_POP = 1.2f;
+    private static final float SCALE_FOR_DISMISS = 0.9f;
 
     private LinearLayout mDismissTarget;
     private ImageView mDismissIcon;
-    private TextView mDismissText;
     private View mDismissCircle;
 
     private SpringAnimation mDismissTargetAlphaSpring;
@@ -54,36 +48,15 @@
         super(context);
         setVisibility(GONE);
 
-        mDismissGradient = new FrameLayout(mContext);
-
-        FrameLayout.LayoutParams gradientParams =
-                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
-        gradientParams.gravity = Gravity.BOTTOM;
-        mDismissGradient.setLayoutParams(gradientParams);
-
-        Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
-        gradient.setAlpha((int) (255 * 0.85f));
-        mDismissGradient.setBackground(gradient);
-
-        mDismissGradient.setVisibility(GONE);
-        addView(mDismissGradient);
-
         LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
         mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
         mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
-        mDismissText = findViewById(R.id.bubble_dismiss_text);
         mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
 
         // Set up the basic target area animations. These are very simple animations that don't need
         // fancy interpolators.
         final AccelerateDecelerateInterpolator interpolator =
                 new AccelerateDecelerateInterpolator();
-        mDismissGradient.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
-        mDismissText.animate()
-                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
-                .setInterpolator(interpolator);
         mDismissIcon.animate()
                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
                 .setInterpolator(interpolator);
@@ -108,110 +81,58 @@
             // safely assume it was animating out rather than in.
             if (alpha < 0.5f) {
                 // If the alpha spring was animating the view out, set it to GONE when it's done.
-                setVisibility(GONE);
+                setVisibility(INVISIBLE);
             }
         });
     }
 
-    /** Springs in the dismiss target and fades in the gradient. */
+    /** Springs in the dismiss target. */
     void springIn() {
         setVisibility(View.VISIBLE);
 
-        // Fade in the dismiss target (icon + text).
+        // Fade in the dismiss target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(1f)
+                .scaleY(1f)
+                .alpha(1f);
         mDismissTarget.setAlpha(0f);
         mDismissTargetAlphaSpring.animateToFinalPosition(1f);
 
-        // Spring up the dismiss target (icon + text).
+        // Spring up the dismiss target.
         mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
         mDismissTargetVerticalSpring.animateToFinalPosition(0);
 
-        // Fade in the gradient.
-        mDismissGradient.setVisibility(VISIBLE);
-        mDismissGradient.animate().alpha(1f);
+        mDismissCircle.setAlpha(0f);
+        mDismissCircle.setScaleX(SCALE_FOR_POP);
+        mDismissCircle.setScaleY(SCALE_FOR_POP);
 
-        // Make sure the dismiss elements are in the separated position (in case we hid the target
-        // while they were condensed to cover the bubbles being in the target).
-        mDismissIcon.setAlpha(1f);
-        mDismissIcon.setScaleX(1f);
-        mDismissIcon.setScaleY(1f);
-        mDismissIcon.setTranslationX(0f);
-        mDismissText.setAlpha(1f);
-        mDismissText.setTranslationX(0f);
+        // Fade in circle and reduce size.
+        mDismissCircle.animate()
+                .alpha(1f)
+                .scaleX(1f)
+                .scaleY(1f);
     }
 
-    /** Springs out the dismiss target and fades out the gradient. */
+    /** Springs out the dismiss target. */
     void springOut() {
+        // Fade out the target icon.
+        mDismissIcon.animate()
+                .setDuration(50)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
+                .alpha(0f);
+
         // Fade out the target.
         mDismissTargetAlphaSpring.animateToFinalPosition(0f);
 
         // Spring the target down a bit.
         mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
 
-        // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
-        mDismissGradient.animate().alpha(0f).withEndAction(
-                () -> mDismissGradient.setVisibility(GONE));
-
-        // Pop out the dismiss circle.
-        mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
-    }
-
-    /**
-     * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
-     * text.
-     */
-    void animateEncircleCenterWithX(boolean encircle) {
-        // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
-        // the X icon over the bubbles), or back to normal if we're un-encircling.
-        final float textTranslation = encircle
-                ? -mDismissIcon.getWidth() / 4f
-                : 0f;
-
-        // Center the icon if we're encircling, or put it back to normal if not.
-        final float iconTranslation = encircle
-                ? mDismissTarget.getWidth() / 2f
-                - mDismissIcon.getWidth() / 2f
-                - mDismissIcon.getLeft()
-                : 0f;
-
-        // Fade in/out the text and translate it.
-        mDismissText.animate()
-                .alpha(encircle ? 0f : 1f)
-                .translationX(textTranslation);
-
-        mDismissIcon.animate()
-                .setDuration(150)
-                .translationX(iconTranslation);
-
-        // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
-        // themselves).
-        mDismissGradient.animate()
-                .alpha(encircle ? 0f : 1f);
-
-        // Prepare the circle to be 'dropped in'.
-        if (encircle) {
-            mDismissCircle.setAlpha(0f);
-            mDismissCircle.setScaleX(1.2f);
-            mDismissCircle.setScaleY(1.2f);
-        }
-
-        // Drop in the circle, or pull it back up.
+        // Pop out the circle.
         mDismissCircle.animate()
-                .alpha(encircle ? 1f : 0f)
-                .scaleX(encircle ? 1f : 0f)
-                .scaleY(encircle ? 1f : 0f);
-    }
-
-    /** Animates the circle and the centered icon out. */
-    void animateEncirclingCircleDisappearance() {
-        // Pop out the dismiss icon and circle.
-        mDismissIcon.animate()
-                .setDuration(50)
-                .scaleX(0.9f)
-                .scaleY(0.9f)
-                .alpha(0f);
-        mDismissCircle.animate()
-                .scaleX(0.9f)
-                .scaleY(0.9f)
+                .scaleX(SCALE_FOR_DISMISS)
+                .scaleY(SCALE_FOR_DISMISS)
                 .alpha(0f);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index de08a8c..521ebde 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -18,14 +18,15 @@
 
 import static android.view.Display.INVALID_DISPLAY;
 
-import static com.android.systemui.bubbles.BubbleController.DEBUG_ENABLE_AUTO_BUBBLE;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
 import android.app.ActivityView;
-import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -35,18 +36,17 @@
 import android.graphics.Color;
 import android.graphics.Insets;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
+import android.os.RemoteException;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.StatsLog;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -54,15 +54,23 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
  */
 public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
-    private static final String TAG = "BubbleExpandedView";
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+
+    private enum ActivityViewStatus {
+        // ActivityView is being initialized, cannot start an activity yet.
+        INITIALIZING,
+        // ActivityView is initialized, and ready to start an activity.
+        INITIALIZED,
+        // Activity runs in the ActivityView.
+        ACTIVITY_STARTED,
+        // ActivityView is released, so activity launching will no longer be permitted.
+        RELEASED,
+    }
 
     // The triangle pointing to the expanded view
     private View mPointerView;
@@ -71,50 +79,90 @@
     private AlphaOptimizedButton mSettingsIcon;
 
     // Views for expanded state
-    private ExpandableNotificationRow mNotifRow;
     private ActivityView mActivityView;
 
-    private boolean mActivityViewReady = false;
+    private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
+    private int mTaskId = -1;
+
     private PendingIntent mBubbleIntent;
 
     private boolean mKeyboardVisible;
     private boolean mNeedsNewHeight;
 
+    private Point mDisplaySize;
     private int mMinHeight;
     private int mSettingsIconHeight;
-    private int mBubbleHeight;
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
+    private Rect mTempRect = new Rect();
+    private int[] mTempLoc = new int[2];
+    private int mExpandedViewTouchSlop;
 
-    private NotificationEntry mEntry;
+    private Bubble mBubble;
     private PackageManager mPm;
     private String mAppName;
     private Drawable mAppIcon;
 
-    private INotificationManager mNotificationManagerService;
     private BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private WindowManager mWindowManager;
 
     private BubbleStackView mStackView;
 
-    private BubbleExpandedView.OnBubbleBlockedListener mOnBubbleBlockedListener;
-
     private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
         @Override
         public void onActivityViewReady(ActivityView view) {
-            if (!mActivityViewReady) {
-                mActivityViewReady = true;
-                // Custom options so there is no activity transition animation
-                ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
-                        0 /* enterResId */, 0 /* exitResId */);
-                // Post to keep the lifecycle normal
-                post(() -> mActivityView.startActivity(mBubbleIntent, options));
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            switch (mActivityViewStatus) {
+                case INITIALIZING:
+                case INITIALIZED:
+                    // Custom options so there is no activity transition animation
+                    ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
+                            0 /* enterResId */, 0 /* exitResId */);
+                    // Post to keep the lifecycle normal
+                    post(() -> {
+                        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                            Log.d(TAG, "onActivityViewReady: calling startActivity, "
+                                    + "bubble=" + getBubbleKey());
+                        }
+                        try {
+                            mActivityView.startActivity(mBubbleIntent, options);
+                        } catch (RuntimeException e) {
+                            // If there's a runtime exception here then there's something
+                            // wrong with the intent, we can't really recover / try to populate
+                            // the bubble again so we'll just remove it.
+                            Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+                                    + ", " + e.getMessage() + "; removing bubble");
+                            mBubbleController.removeBubble(mBubble.getKey(),
+                                    BubbleController.DISMISS_INVALID_INTENT);
+                        }
+                    });
+                    mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;
             }
         }
 
         @Override
         public void onActivityViewDestroyed(ActivityView view) {
-            mActivityViewReady = false;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            mActivityViewStatus = ActivityViewStatus.RELEASED;
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskCreated: taskId=" + taskId
+                        + " bubble=" + getBubbleKey());
+            }
+            // Since Bubble ActivityView applies singleTaskDisplay this is
+            // guaranteed to only be called once per ActivityView. The taskId is
+            // saved to use for removeTask, preventing appearance in recent tasks.
+            mTaskId = taskId;
         }
 
         /**
@@ -125,9 +173,14 @@
          */
         @Override
         public void onTaskRemovalStarted(int taskId) {
-            if (mEntry != null) {
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+                        + " mActivityViewStatus=" + mActivityViewStatus
+                        + " bubble=" + getBubbleKey());
+            }
+            if (mBubble != null) {
                 // Must post because this is called from a binder thread.
-                post(() -> mBubbleController.removeBubble(mEntry.key,
+                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
                         BubbleController.DISMISS_TASK_FINISHED));
             }
         }
@@ -149,20 +202,22 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mPm = context.getPackageManager();
-        mMinHeight = getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_default_height);
-        mPointerMargin = getResources().getDimensionPixelSize(R.dimen.bubble_pointer_margin);
-        try {
-            mNotificationManagerService = INotificationManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
-        } catch (ServiceManager.ServiceNotFoundException e) {
-            Log.w(TAG, e);
-        }
+        mDisplaySize = new Point();
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+        Resources res = getResources();
+        mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
+        mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
+        mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey());
+        }
 
         Resources res = getResources();
         mPointerView = findViewById(R.id.pointer_view);
@@ -173,10 +228,10 @@
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
         mPointerView.setBackground(mPointerDrawable);
-        mPointerView.setVisibility(GONE);
+        mPointerView.setVisibility(INVISIBLE);
 
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
-                R.dimen.bubble_expanded_header_height);
+                R.dimen.bubble_settings_size);
         mSettingsIcon = findViewById(R.id.settings_button);
         mSettingsIcon.setOnClickListener(this);
 
@@ -210,6 +265,10 @@
         });
     }
 
+    private String getBubbleKey() {
+        return mBubble != null ? mBubble.getKey() : "null";
+    }
+
     void applyThemeAttrs() {
         TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView);
         int bgColor = ta.getColor(
@@ -235,6 +294,9 @@
         if (mActivityView != null) {
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0));
         }
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
+        }
     }
 
     /**
@@ -246,6 +308,10 @@
      * and setting {@code false} actually means rendering the contents in transparent.
      */
     void setContentVisibility(boolean visibility) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setContentVisibility: visibility=" + visibility
+                    + " bubble=" + getBubbleKey());
+        }
         final float alpha = visibility ? 1f : 0f;
         mPointerView.setAlpha(alpha);
         if (mActivityView != null) {
@@ -259,44 +325,37 @@
      */
     void updateInsets(WindowInsets insets) {
         if (usingActivityView()) {
-            Point displaySize = new Point();
-            mActivityView.getContext().getDisplay().getSize(displaySize);
-            int[] windowLocation = mActivityView.getLocationOnScreen();
-            final int windowBottom = windowLocation[1] + mActivityView.getHeight();
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
-            final int insetsBottom = Math.max(0,
-                    windowBottom + keyboardHeight - displaySize.y);
+            int[] screenLoc = mActivityView.getLocationOnScreen();
+            final int activityViewBottom = screenLoc[1] + mActivityView.getHeight();
+            final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(),
+                    insets.getDisplayCutout() != null
+                            ? insets.getDisplayCutout().getSafeInsetBottom()
+                            : 0);
+            final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0);
             mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom));
         }
     }
 
     /**
-     * Sets the listener to notify when a bubble has been blocked.
+     * Sets the bubble used to populate this view.
      */
-    public void setOnBlockedListener(OnBubbleBlockedListener listener) {
-        mOnBubbleBlockedListener = listener;
-    }
+    public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
 
-    /**
-     * Sets the notification entry used to populate this view.
-     */
-    public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
         mStackView = stackView;
-        mEntry = entry;
+        mBubble = bubble;
         mAppName = appName;
 
-        ApplicationInfo info;
         try {
-            info = mPm.getApplicationInfo(
-                    entry.notification.getPackageName(),
+            ApplicationInfo info = mPm.getApplicationInfo(
+                    bubble.getPackageName(),
                     PackageManager.MATCH_UNINSTALLED_PACKAGES
                             | PackageManager.MATCH_DISABLED_COMPONENTS
                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
-            if (info != null) {
-                mAppIcon = mPm.getApplicationIcon(info);
-            }
+            mAppIcon = mPm.getApplicationIcon(info);
         } catch (PackageManager.NameNotFoundException e) {
             // Do nothing.
         }
@@ -312,52 +371,46 @@
      * Lets activity view know it should be shown / populated.
      */
     public void populateExpandedView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "populateExpandedView: "
+                    + "bubble=" + getBubbleKey());
+        }
+
         if (usingActivityView()) {
             mActivityView.setCallback(mStateCallback);
         } else {
-            // We're using notification template
-            ViewGroup parent = (ViewGroup) mNotifRow.getParent();
-            if (parent == this) {
-                // Already added
-                return;
-            } else if (parent != null) {
-                // Still in the shade... remove it
-                parent.removeView(mNotifRow);
-            }
-            addView(mNotifRow, 1 /* index */);
-            mPointerView.setAlpha(1f);
+            Log.e(TAG, "Cannot populate expanded view.");
         }
     }
 
     /**
-     * Updates the entry backing this view. This will not re-populate ActivityView, it will
+     * Updates the bubble backing this view. This will not re-populate ActivityView, it will
      * only update the deep-links in the title, and the height of the view.
      */
-    public void update(NotificationEntry entry) {
-        if (entry.key.equals(mEntry.key)) {
-            mEntry = entry;
+    public void update(Bubble bubble) {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
+        }
+        if (bubble.getKey().equals(mBubble.getKey())) {
+            mBubble = bubble;
             updateSettingsContentDescription();
             updateHeight();
         } else {
-            Log.w(TAG, "Trying to update entry with different key, new entry: "
-                    + entry.key + " old entry: " + mEntry.key);
+            Log.w(TAG, "Trying to update entry with different key, new bubble: "
+                    + bubble.getKey() + " old bubble: " + bubble.getKey());
         }
     }
 
     private void updateExpandedView() {
-        mBubbleIntent = getBubbleIntent(mEntry);
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateExpandedView: bubble="
+                    + getBubbleKey());
+        }
+
+        mBubbleIntent = mBubble.getBubbleIntent(mContext);
         if (mBubbleIntent != null) {
-            if (mNotifRow != null) {
-                // Clear out the row if we had it previously
-                removeView(mNotifRow);
-                mNotifRow = null;
-            }
             setContentVisibility(false);
             mActivityView.setVisibility(VISIBLE);
-        } else if (DEBUG_ENABLE_AUTO_BUBBLE) {
-            // Hide activity view if we had it previously
-            mActivityView.setVisibility(GONE);
-            mNotifRow = mEntry.getRow();
         }
         updateView();
     }
@@ -371,29 +424,12 @@
     }
 
     void updateHeight() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
+        }
         if (usingActivityView()) {
-            Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
-            float desiredHeight;
-            if (data == null) {
-                // This is a contentIntent based bubble, lets allow it to be the max height
-                // as it was forced into this mode and not prepared to be small
-                desiredHeight = mStackView.getMaxExpandedHeight();
-            } else {
-                boolean useRes = data.getDesiredHeightResId() != 0;
-                float desiredPx;
-                if (useRes) {
-                    desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
-                            mEntry.notification.getPackageName(),
-                            mEntry.notification.getUser().getIdentifier());
-                } else {
-                    desiredPx = data.getDesiredHeight()
-                            * getContext().getResources().getDisplayMetrics().density;
-                }
-                desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
-            }
-            int max = mStackView.getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
-                    - mPointerMargin;
-            float height = Math.min(desiredHeight, max);
+            float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
+            float height = Math.min(desiredHeight, getMaxExpandedHeight());
             height = Math.max(height, mMinHeight);
             LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
             mNeedsNewHeight =  lp.height != height;
@@ -401,28 +437,66 @@
                 // If the keyboard is visible... don't adjust the height because that will cause
                 // a configuration change and the keyboard will be lost.
                 lp.height = (int) height;
-                mBubbleHeight = (int) height;
                 mActivityView.setLayoutParams(lp);
                 mNeedsNewHeight = false;
             }
-        } else {
-            mBubbleHeight = mNotifRow != null ? mNotifRow.getIntrinsicHeight() : mMinHeight;
+            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+                Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
+                        + " mNeedsNewHeight=" + mNeedsNewHeight);
+            }
         }
     }
 
+    private int getMaxExpandedHeight() {
+        mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
+        int[] windowLocation = mActivityView.getLocationOnScreen();
+        int bottomInset = getRootWindowInsets() != null
+                ? getRootWindowInsets().getStableInsetBottom()
+                : 0;
+        return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
+                - mPointerMargin - bottomInset;
+    }
+
+    /**
+     * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
+     * expanded view.
+     *
+     * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
+     */
+    boolean intersectingTouchableContent(int rawX, int rawY) {
+        mTempRect.setEmpty();
+        if (mActivityView != null) {
+            mTempLoc = mActivityView.getLocationOnScreen();
+            mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
+                    mTempLoc[1] - mExpandedViewTouchSlop,
+                    mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
+                    mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
+        }
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        mTempLoc = mSettingsIcon.getLocationOnScreen();
+        mTempRect.set(mTempLoc[0],
+                mTempLoc[1],
+                mTempLoc[0] + mSettingsIcon.getWidth(),
+                mTempLoc[1] + mSettingsIcon.getHeight());
+        if (mTempRect.contains(rawX, rawY)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void onClick(View view) {
-        if (mEntry == null) {
+        if (mBubble == null) {
             return;
         }
-        Notification n = mEntry.notification.getNotification();
         int id = view.getId();
         if (id == R.id.settings_button) {
-            Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
-                    mEntry.notification.getUid());
+            Intent intent = mBubble.getSettingsIntent();
             mStackView.collapseStack(() -> {
-                mContext.startActivityAsUser(intent, mEntry.notification.getUser());
-                logBubbleClickEvent(mEntry,
+                mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser());
+                logBubbleClickEvent(mBubble,
                         StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
             });
         }
@@ -442,13 +516,14 @@
      * Update appearance of the expanded view being displayed.
      */
     public void updateView() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "updateView: bubble="
+                    + getBubbleKey());
+        }
         if (usingActivityView()
                 && mActivityView.getVisibility() == VISIBLE
                 && mActivityView.isAttachedToWindow()) {
             mActivityView.onLocationChanged();
-        } else if (mNotifRow != null) {
-            applyRowState(mNotifRow);
-            mPointerView.setAlpha(1f);
         }
         updateHeight();
     }
@@ -467,17 +542,44 @@
      * Removes and releases an ActivityView if one was previously created for this bubble.
      */
     public void cleanUpExpandedState() {
-        removeView(mNotifRow);
-
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus
+                    + ", bubble=" + getBubbleKey());
+        }
         if (mActivityView == null) {
             return;
         }
-        if (mActivityViewReady) {
-            mActivityView.release();
+        switch (mActivityViewStatus) {
+            case INITIALIZED:
+            case ACTIVITY_STARTED:
+                mActivityView.release();
+        }
+        if (mTaskId != -1) {
+            try {
+                ActivityTaskManager.getService().removeTask(mTaskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to remove taskId " + mTaskId);
+            }
+            mTaskId = -1;
         }
         removeView(mActivityView);
+
         mActivityView = null;
-        mActivityViewReady = false;
+    }
+
+    /**
+     * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay}
+     * which {@link ActivityView} uses.
+     */
+    void notifyDisplayEmpty() {
+        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+            Log.d(TAG, "notifyDisplayEmpty: bubble="
+                    + getBubbleKey()
+                    + " mActivityViewStatus=" + mActivityViewStatus);
+        }
+        if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
+            mActivityViewStatus = ActivityViewStatus.INITIALIZED;
+        }
     }
 
     private boolean usingActivityView() {
@@ -494,76 +596,14 @@
         return INVALID_DISPLAY;
     }
 
-    private void applyRowState(ExpandableNotificationRow view) {
-        view.reset();
-        view.setHeadsUp(false);
-        view.resetTranslation();
-        view.setOnKeyguard(false);
-        view.setClipBottomAmount(0);
-        view.setClipTopAmount(0);
-        view.setContentTransformationAmount(0, false);
-        view.setIconsVisible(true);
-
-        // TODO - Need to reset this (and others) when view goes back in shade, leave for now
-        // view.setTopRoundness(1, false);
-        // view.setBottomRoundness(1, false);
-
-        ExpandableViewState viewState = view.getViewState();
-        viewState = viewState == null ? new ExpandableViewState() : viewState;
-        viewState.height = view.getIntrinsicHeight();
-        viewState.gone = false;
-        viewState.hidden = false;
-        viewState.dimmed = false;
-        viewState.alpha = 1f;
-        viewState.notGoneIndex = -1;
-        viewState.xTranslation = 0;
-        viewState.yTranslation = 0;
-        viewState.zTranslation = 0;
-        viewState.scaleX = 1;
-        viewState.scaleY = 1;
-        viewState.inShelf = true;
-        viewState.headsUpIsVisible = false;
-        viewState.applyToView(view);
-    }
-
-    private Intent getSettingsIntent(String packageName, final int appUid) {
-        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
-        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
-        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
-        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
-        return intent;
-    }
-
-    @Nullable
-    private PendingIntent getBubbleIntent(NotificationEntry entry) {
-        Notification notif = entry.notification.getNotification();
-        Notification.BubbleMetadata data = notif.getBubbleMetadata();
-        if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) {
-            return data.getIntent();
-        }
-        return null;
-    }
-
-    /**
-     * Listener that is notified when a bubble is blocked.
-     */
-    public interface OnBubbleBlockedListener {
-        /**
-         * Called when a bubble is blocked for the provided entry.
-         */
-        void onBubbleBlocked(NotificationEntry entry);
-    }
-
     /**
      * Logs bubble UI click event.
      *
-     * @param entry the bubble notification entry that user is interacting with.
+     * @param bubble the bubble notification entry that user is interacting with.
      * @param action the user interaction enum.
      */
-    private void logBubbleClickEvent(NotificationEntry entry, int action) {
-        StatusBarNotification notification = entry.notification;
+    private void logBubbleClickEvent(Bubble bubble, int action) {
+        StatusBarNotification notification = bubble.getEntry().notification;
         StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                 notification.getPackageName(),
                 notification.getNotification().getChannelId(),
@@ -573,27 +613,8 @@
                 action,
                 mStackView.getNormalizedXPosition(),
                 mStackView.getNormalizedYPosition(),
-                entry.showInShadeWhenBubble(),
-                entry.isForegroundService(),
-                BubbleController.isForegroundApp(mContext, notification.getPackageName()));
-    }
-
-    private int getDimenForPackageUser(int resId, String pkg, int userId) {
-        Resources r;
-        if (pkg != null) {
-            try {
-                if (userId == UserHandle.USER_ALL) {
-                    userId = UserHandle.USER_SYSTEM;
-                }
-                r = mPm.getResourcesForApplicationAsUser(pkg, userId);
-                return r.getDimensionPixelSize(resId);
-            } catch (PackageManager.NameNotFoundException ex) {
-                // Uninstalled, don't care
-            } catch (Resources.NotFoundException e) {
-                // Invalid res id, return 0 and user our default
-                Log.e(TAG, "Couldn't find desired height res id", e);
-            }
-        }
-        return 0;
+                bubble.showInShadeWhenBubble(),
+                bubble.isOngoing(),
+                false /* isAppForeground (unused) */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
index 71f68c1..58f3f22 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
@@ -39,8 +39,7 @@
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
@@ -57,6 +56,9 @@
     private final int mFlyoutSpaceFromBubble;
     private final int mPointerSize;
     private final int mBubbleSize;
+    private final int mBubbleIconBitmapSize;
+    private final float mBubbleIconTopPadding;
+
     private final int mFlyoutElevation;
     private final int mBubbleElevation;
     private final int mFloatingBackgroundColor;
@@ -64,14 +66,11 @@
 
     private final ViewGroup mFlyoutTextContainer;
     private final TextView mFlyoutText;
-    /** Spring animation for the flyout. */
-    private final SpringAnimation mFlyoutSpring =
-            new SpringAnimation(this, DynamicAnimation.TRANSLATION_X);
 
     /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
     private final float mNewDotRadius;
     private final float mNewDotSize;
-    private final float mNewDotOffsetFromBubbleBounds;
+    private final float mOriginalDotSize;
 
     /**
      * The paint used to draw the background, whose color changes as the flyout transitions to the
@@ -113,7 +112,6 @@
      */
     private float mFlyoutToDotWidthDelta = 0f;
     private float mFlyoutToDotHeightDelta = 0f;
-    private float mFlyoutToDotCornerRadiusDelta;
 
     /** The translation values when the flyout is completely transitioned into the dot. */
     private float mTranslationXWhenDot = 0f;
@@ -126,11 +124,18 @@
     private float mBgTranslationX;
     private float mBgTranslationY;
 
+    private float[] mDotCenter;
+
     /** The flyout's X translation when at rest (not animating or dragging). */
     private float mRestingTranslationX = 0f;
 
+    /** The badge sizes are defined as percentages of the app icon size. Same value as Launcher3. */
+    private static final float SIZE_PERCENTAGE = 0.228f;
+
+    private static final float DOT_SCALE = 1f;
+
     /** Callback to run when the flyout is hidden. */
-    private Runnable mOnHide;
+    @Nullable private Runnable mOnHide;
 
     public BubbleFlyoutView(Context context) {
         super(context);
@@ -143,11 +148,16 @@
         mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
         mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
         mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
+
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubbleIconTopPadding  = (mBubbleSize - mBubbleIconBitmapSize) / 2f;
+
         mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
         mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
-        mNewDotOffsetFromBubbleBounds = BadgeRenderer.getDotCenterOffset(context);
-        mNewDotRadius = BadgeRenderer.getDotRadius(mNewDotOffsetFromBubbleBounds);
+
+        mOriginalDotSize = SIZE_PERCENTAGE * mBubbleIconBitmapSize;
+        mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
         mNewDotSize = mNewDotRadius * 2f;
 
         final TypedArray ta = mContext.obtainStyledAttributes(
@@ -156,7 +166,6 @@
                         android.R.attr.dialogCornerRadius});
         mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
         mCornerRadius = ta.getDimensionPixelSize(1, 0);
-        mFlyoutToDotCornerRadiusDelta = mNewDotRadius - mCornerRadius;
         ta.recycle();
 
         // Add padding for the pointer on either side, onDraw will draw it in this space.
@@ -193,17 +202,17 @@
         super.onDraw(canvas);
     }
 
-    /** Configures the flyout and animates it in. */
-    void showFlyout(
+    /** Configures the flyout, collapsed into to dot form. */
+    void setupFlyoutStartingAsDot(
             CharSequence updateMessage, PointF stackPos, float parentWidth,
-            boolean arrowPointingLeft, int dotColor, Runnable onHide) {
+            boolean arrowPointingLeft, int dotColor, @Nullable Runnable onLayoutComplete,
+            @Nullable Runnable onHide, float[] dotCenter) {
         mArrowPointingLeft = arrowPointingLeft;
         mDotColor = dotColor;
         mOnHide = onHide;
+        mDotCenter = dotCenter;
 
-        setCollapsePercent(0f);
-        setAlpha(0f);
-        setVisibility(VISIBLE);
+        setCollapsePercent(1f);
 
         // Set the flyout TextView's max width in terms of percent, and then subtract out the
         // padding so that the entire flyout view will be the desired width (rather than the
@@ -214,14 +223,16 @@
 
         // Wait for the TextView to lay out so we know its line count.
         post(() -> {
+            float restingTranslationY;
             // Multi line flyouts get top-aligned to the bubble.
             if (mFlyoutText.getLineCount() > 1) {
-                setTranslationY(stackPos.y);
+                restingTranslationY = stackPos.y + mBubbleIconTopPadding;
             } else {
                 // Single line flyouts are vertically centered with respect to the bubble.
-                setTranslationY(
-                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f);
+                restingTranslationY =
+                        stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
             }
+            setTranslationY(restingTranslationY);
 
             // Calculate the translation required to position the flyout next to the bubble stack,
             // with the desired padding.
@@ -229,40 +240,30 @@
                     ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
                     : stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
 
-            // Translate towards the stack slightly.
-            setTranslationX(
-                    mRestingTranslationX + (arrowPointingLeft ? -mBubbleSize : mBubbleSize));
-
-            // Fade in the entire flyout and spring it to its normal position.
-            animate().alpha(1f);
-            mFlyoutSpring.animateToFinalPosition(mRestingTranslationX);
-
             // Calculate the difference in size between the flyout and the 'dot' so that we can
             // transform into the dot later.
             mFlyoutToDotWidthDelta = getWidth() - mNewDotSize;
             mFlyoutToDotHeightDelta = getHeight() - mNewDotSize;
 
             // Calculate the translation values needed to be in the correct 'new dot' position.
-            final float distanceFromFlyoutLeftToDotCenterX =
-                    mFlyoutSpaceFromBubble + mNewDotOffsetFromBubbleBounds / 2;
-            if (mArrowPointingLeft) {
-                mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
-            } else {
-                mTranslationXWhenDot =
-                        getWidth() + distanceFromFlyoutLeftToDotCenterX - mNewDotRadius;
-            }
+            final float dotPositionX = stackPos.x + mDotCenter[0] - (mOriginalDotSize / 2f);
+            final float dotPositionY = stackPos.y + mDotCenter[1] - (mOriginalDotSize / 2f);
 
-            mTranslationYWhenDot =
-                    getHeight() / 2f
-                            - mNewDotRadius
-                            - mBubbleSize / 2f
-                            + mNewDotOffsetFromBubbleBounds / 2;
+            final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
+            final float distanceFromLayoutTopToDotCenterY = restingTranslationY - dotPositionY;
+
+            mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
+            mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
+            if (onLayoutComplete != null) {
+                onLayoutComplete.run();
+            }
         });
     }
 
     /**
-     * Hides the flyout and runs the optional callback passed into showFlyout. The flyout has been
-     * animated into the 'new' dot by the time we call this, so no animations are needed.
+     * Hides the flyout and runs the optional callback passed into setupFlyoutStartingAsDot.
+     * The flyout has been animated into the 'new' dot by the time we call this, so no animations
+     * are needed.
      */
     void hideFlyout() {
         if (mOnHide != null) {
@@ -275,6 +276,13 @@
 
     /** Sets the percentage that the flyout should be collapsed into dot form. */
     void setCollapsePercent(float percentCollapsed) {
+        // This is unlikely, but can happen in a race condition where the flyout view hasn't been
+        // laid out and returns 0 for getWidth(). We check for this condition at the sites where
+        // this method is called, but better safe than sorry.
+        if (Float.isNaN(percentCollapsed)) {
+            return;
+        }
+
         mPercentTransitionedToDot = Math.max(0f, Math.min(percentCollapsed, 1f));
         mPercentStillFlyout = (1f - mPercentTransitionedToDot);
 
@@ -311,8 +319,8 @@
         // percentage.
         final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot);
         final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot);
-        final float cornerRadius = mCornerRadius
-                - (mFlyoutToDotCornerRadiusDelta * mPercentTransitionedToDot);
+        final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+                + mCornerRadius * (1 - mPercentTransitionedToDot);
 
         // Translate the flyout background towards the collapsed 'dot' state.
         mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot;
@@ -336,7 +344,7 @@
         canvas.save();
         canvas.translate(mBgTranslationX, mBgTranslationY);
         renderPointerTriangle(canvas, width, height);
-        canvas.drawRoundRect(mBgRect, cornerRadius, cornerRadius, mBgPaint);
+        canvas.drawRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, mBgPaint);
         canvas.restore();
     }
 
@@ -379,7 +387,10 @@
         if (!mTriangleOutline.isEmpty()) {
             // Draw the rect into the outline as a path so we can merge the triangle path into it.
             final Path rectPath = new Path();
-            rectPath.addRoundRect(mBgRect, mCornerRadius, mCornerRadius, Path.Direction.CW);
+            final float interpolatedRadius = mNewDotRadius * mPercentTransitionedToDot
+                    + mCornerRadius * (1 - mPercentTransitionedToDot);
+            rectPath.addRoundRect(mBgRect, interpolatedRadius,
+                    interpolatedRadius, Path.Direction.CW);
             outline.setConvexPath(rectPath);
 
             // Get rid of the triangle path once it has disappeared behind the flyout.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
new file mode 100644
index 0000000..a1c77c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.bubbles;
+
+import android.content.Context;
+
+import com.android.launcher3.icons.BaseIconFactory;
+import com.android.systemui.R;
+
+/**
+ * Factory for creating normalized bubble icons.
+ * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
+ * so there is no need to manage a pool across multiple threads.
+ */
+public class BubbleIconFactory extends BaseIconFactory {
+    protected BubbleIconFactory(Context context) {
+        super(context, context.getResources().getConfiguration().densityDpi,
+                context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
+    }
+
+    public int getBadgeSize() {
+        return mContext.getResources().getDimensionPixelSize(
+                com.android.launcher3.icons.R.dimen.profile_badge_size);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index f87bcef..31cf853 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,16 +19,20 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -41,11 +45,11 @@
 import android.util.Log;
 import android.util.StatsLog;
 import android.view.Choreographer;
+import android.view.DisplayCutout;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -69,6 +73,8 @@
 import com.android.systemui.bubbles.animation.StackAnimationController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.ArrayList;
@@ -79,8 +85,7 @@
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
 public class BubbleStackView extends FrameLayout {
-    private static final String TAG = "BubbleStackView";
-    private static final boolean DEBUG = false;
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
 
     /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
     static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
@@ -160,9 +165,14 @@
     private BubbleFlyoutView mFlyout;
     /** Runnable that fades out the flyout and then sets it to GONE. */
     private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
+    /**
+     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
+     * previous one animates out.
+     */
+    private Runnable mFlyoutOnHide;
 
     /** Layout change listener that moves the stack to the nearest valid position on rotation. */
-    private OnLayoutChangeListener mMoveStackToValidPositionOnLayoutListener;
+    private OnLayoutChangeListener mOrientationChangedListener;
     /** Whether the stack was on the left side of the screen prior to rotation. */
     private boolean mWasOnLeftBeforeRotation = false;
     /**
@@ -172,18 +182,17 @@
     private float mVerticalPosPercentBeforeRotation = -1;
 
     private int mBubbleSize;
-    private int mBubblePadding;
+    private int mBubblePaddingTop;
+    private int mBubbleTouchPadding;
     private int mExpandedViewPadding;
     private int mExpandedAnimateXDistance;
     private int mExpandedAnimateYDistance;
     private int mPointerHeight;
     private int mStatusBarHeight;
-    private int mPipDismissHeight;
     private int mImeOffset;
-
+    private BubbleIconFactory mBubbleIconFactory;
     private Bubble mExpandedBubble;
     private boolean mIsExpanded;
-    private boolean mImeVisible;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
     private boolean mStackOnLeftOrWillBe = false;
@@ -191,9 +200,20 @@
     /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
     private boolean mIsGestureInProgress = false;
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Stack view state:");
+        pw.print("  gestureInProgress:    "); pw.println(mIsGestureInProgress);
+        pw.print("  showingDismiss:       "); pw.println(mShowingDismiss);
+        pw.print("  isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
+        pw.print("  draggingInDismiss:    "); pw.println(mDraggingInDismissTarget);
+        pw.print("  animatingMagnet:      "); pw.println(mAnimatingMagnet);
+        mStackAnimationController.dump(fd, pw, args);
+        mExpandedAnimationController.dump(fd, pw, args);
+    }
+
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
-    private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
 
     private boolean mViewUpdatedRequested = false;
     private boolean mIsExpansionAnimating = false;
@@ -225,7 +245,7 @@
                 @Override
                 public boolean onPreDraw() {
                     getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
-                    applyCurrentState();
+                    updateExpandedView();
                     mViewUpdatedRequested = false;
                     return true;
                 }
@@ -270,6 +290,11 @@
     private float mFlyoutDragDeltaX = 0f;
 
     /**
+     * Runnable that animates in the flyout. This reference is needed to cancel delayed postings.
+     */
+    private Runnable mAnimateInFlyout;
+
+    /**
      * End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
      * it immediately.
      */
@@ -282,13 +307,13 @@
                 }
             };
 
-    @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+    @NonNull
+    private final SurfaceSynchronizer mSurfaceSynchronizer;
 
     private BubbleDismissView mDismissContainer;
     private Runnable mAfterMagnet;
 
-    private boolean mSuppressNewDot = false;
-    private boolean mSuppressFlyout = false;
+    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer) {
@@ -302,7 +327,8 @@
 
         Resources res = getResources();
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+        mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
         mExpandedAnimateXDistance =
                 res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
         mExpandedAnimateYDistance =
@@ -311,13 +337,12 @@
 
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.pip_dismiss_gradient_height);
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
 
         mDisplaySize = new Point();
         WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getSize(mDisplaySize);
+        // We use the real size & subtract screen decorations / window insets ourselves when needed
+        wm.getDefaultDisplay().getRealSize(mDisplaySize);
 
         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
 
@@ -325,8 +350,9 @@
         int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
 
         mStackAnimationController = new StackAnimationController();
+
         mExpandedAnimationController = new ExpandedAnimationController(
-                mDisplaySize, mExpandedViewPadding);
+                mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation);
         mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
 
         mBubbleContainer = new PhysicsAnimationLayout(context);
@@ -335,6 +361,8 @@
         mBubbleContainer.setClipChildren(false);
         addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
 
+        mBubbleIconFactory = new BubbleIconFactory(context);
+
         mExpandedViewContainer = new FrameLayout(context);
         mExpandedViewContainer.setElevation(elevation);
         mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
@@ -342,15 +370,9 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
-        mFlyout = new BubbleFlyoutView(context);
-        mFlyout.setVisibility(GONE);
-        mFlyout.animate()
-                .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
-                .setInterpolator(new AccelerateDecelerateInterpolator());
-        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-
+        setUpFlyout();
         mFlyoutTransitionSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setStiffness(SpringForce.STIFFNESS_LOW)
                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
 
@@ -361,13 +383,6 @@
                 Gravity.BOTTOM));
         addView(mDismissContainer);
 
-        mDismissContainer = new BubbleDismissView(mContext);
-        mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
-                MATCH_PARENT,
-                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
-                Gravity.BOTTOM));
-        addView(mDismissContainer);
-
         mExpandedViewXAnim =
                 new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
         mExpandedViewXAnim.setSpring(
@@ -383,7 +398,7 @@
                         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
         mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
             if (mIsExpanded && mExpandedBubble != null) {
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             }
         });
 
@@ -392,34 +407,58 @@
         mBubbleContainer.bringToFront();
 
         setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
-            final int keyboardHeight = insets.getSystemWindowInsetBottom()
-                    - insets.getStableInsetBottom();
             if (!mIsExpanded || mIsExpansionAnimating) {
                 return view.onApplyWindowInsets(insets);
             }
-            mImeVisible = keyboardHeight != 0;
-
-            float newY = getYPositionForExpandedView();
-            if (newY < 0) {
-                // TODO: This means our expanded content is too big to fit on screen. Right now
-                // we'll let it translate off but we should be clipping it & pushing the header
-                // down so that it always remains visible.
-            }
-            mExpandedViewYAnim.animateToFinalPosition(newY);
             mExpandedAnimationController.updateYPosition(
                     // Update the insets after we're done translating otherwise position
                     // calculation for them won't be correct.
-                    () -> mExpandedBubble.expandedView.updateInsets(insets));
+                    () -> mExpandedBubble.getExpandedView().updateInsets(insets));
             return view.onApplyWindowInsets(insets);
         });
 
-        mMoveStackToValidPositionOnLayoutListener =
+        mOrientationChangedListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    mExpandedAnimationController.updateOrientation(mOrientation, mDisplaySize);
+                    mStackAnimationController.updateOrientation(mOrientation);
+
+                    // Reposition & adjust the height for new orientation
+                    if (mIsExpanded) {
+                        mExpandedViewContainer.setTranslationY(getExpandedViewY());
+                        mExpandedBubble.getExpandedView().updateView();
+                    }
+
+                    // Need to update the padding around the view
+                    WindowInsets insets = getRootWindowInsets();
+                    int leftPadding = mExpandedViewPadding;
+                    int rightPadding = mExpandedViewPadding;
+                    if (insets != null) {
+                        // Can't have the expanded view overlaying notches
+                        int cutoutLeft = 0;
+                        int cutoutRight = 0;
+                        DisplayCutout cutout = insets.getDisplayCutout();
+                        if (cutout != null) {
+                            cutoutLeft = cutout.getSafeInsetLeft();
+                            cutoutRight = cutout.getSafeInsetRight();
+                        }
+                        // Or overlaying nav or status bar
+                        leftPadding += Math.max(cutoutLeft, insets.getStableInsetLeft());
+                        rightPadding += Math.max(cutoutRight, insets.getStableInsetRight());
+                    }
+                    mExpandedViewContainer.setPadding(leftPadding, mExpandedViewPadding,
+                            rightPadding, mExpandedViewPadding);
+
+                    if (mIsExpanded) {
+                        // Re-draw bubble row and pointer for new orientation.
+                        mExpandedAnimationController.expandFromStack(() -> {
+                            updatePointerPosition();
+                        } /* after */);
+                    }
                     if (mVerticalPosPercentBeforeRotation >= 0) {
                         mStackAnimationController.moveStackToSimilarPositionAfterRotation(
                                 mWasOnLeftBeforeRotation, mVerticalPosPercentBeforeRotation);
                     }
-                    removeOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
+                    removeOnLayoutChangeListener(mOrientationChangedListener);
                 };
 
         // This must be a separate OnDrawListener since it should be called for every draw.
@@ -449,25 +488,53 @@
         });
     }
 
+    private void setUpFlyout() {
+        if (mFlyout != null) {
+            removeView(mFlyout);
+        }
+        mFlyout = new BubbleFlyoutView(getContext());
+        mFlyout.setVisibility(GONE);
+        mFlyout.animate()
+                .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
+                .setInterpolator(new AccelerateDecelerateInterpolator());
+        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+    }
+
     /**
      * Handle theme changes.
      */
     public void onThemeChanged() {
+        // Recreate icon factory to update default adaptive icon scale.
+        mBubbleIconFactory = new BubbleIconFactory(mContext);
+        setUpFlyout();
         for (Bubble b: mBubbleData.getBubbles()) {
-            b.iconView.updateViews();
-            b.expandedView.applyThemeAttrs();
+            b.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+            b.getIconView().updateViews();
+            b.getExpandedView().applyThemeAttrs();
         }
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
-    public void onOrientationChanged() {
+    public void onOrientationChanged(int orientation) {
+        mOrientation = orientation;
+
+        // Display size is based on the rotation device was in when requested, we should update it
+        // We use the real size & subtract screen decorations / window insets ourselves when needed
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getRealSize(mDisplaySize);
+
+        // Some resources change depending on orientation
+        Resources res = getContext().getResources();
+        mStatusBarHeight = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+
         final RectF allowablePos = mStackAnimationController.getAllowableStackPositionRegion();
         mWasOnLeftBeforeRotation = mStackAnimationController.isStackOnLeftSide();
         mVerticalPosPercentBeforeRotation =
                 (mStackAnimationController.getStackPosition().y - allowablePos.top)
                         / (allowablePos.bottom - allowablePos.top);
-        addOnLayoutChangeListener(mMoveStackToValidPositionOnLayoutListener);
-
+        addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
     }
 
@@ -483,18 +550,6 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        float x = ev.getRawX();
-        float y = ev.getRawY();
-        // If we're expanded only intercept if the tap is outside of the widget container
-        if (mIsExpanded && isIntersecting(mExpandedViewContainer, x, y)) {
-            return false;
-        } else {
-            return isIntersecting(mBubbleContainer, x, y);
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
 
@@ -570,7 +625,7 @@
         }
         Bubble topBubble = mBubbleData.getBubbles().get(0);
         String appName = topBubble.getAppName();
-        Notification notification = topBubble.entry.notification.getNotification();
+        Notification notification = topBubble.getEntry().notification.getNotification();
         CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
         String titleStr = getResources().getString(R.string.stream_notification);
         if (titleCharSeq != null) {
@@ -616,6 +671,7 @@
 
     /**
      * Updates the visibility of the 'dot' indicating an update on the bubble.
+     *
      * @param key the {@link NotificationEntry#key} associated with the bubble.
      */
     public void updateDotVisibility(String key) {
@@ -640,10 +696,17 @@
     }
 
     /**
+     * Whether the stack of bubbles is animating to or from expansion.
+     */
+    public boolean isExpansionAnimating() {
+        return mIsExpansionAnimating;
+    }
+
+    /**
      * The {@link BubbleView} that is expanded, null if one does not exist.
      */
     BubbleView getExpandedBubbleView() {
-        return mExpandedBubble != null ? mExpandedBubble.iconView : null;
+        return mExpandedBubble != null ? mExpandedBubble.getIconView() : null;
     }
 
     /**
@@ -664,36 +727,33 @@
         Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
         if (bubbleToExpand != null) {
             setSelectedBubble(bubbleToExpand);
-            bubbleToExpand.entry.setShowInShadeWhenBubble(false);
+            bubbleToExpand.setShowInShadeWhenBubble(false);
             setExpanded(true);
         }
     }
 
-    /**
-     * Sets the entry that should be expanded and expands if needed.
-     */
-    @VisibleForTesting
-    void setExpandedBubble(NotificationEntry entry) {
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            if (entry.equals(bv.getEntry())) {
-                setExpandedBubble(entry.key);
-            }
-        }
-    }
-
     // via BubbleData.Listener
     void addBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "addBubble: " + bubble);
         }
-        bubble.inflate(mInflater, this);
-        mBubbleContainer.addView(bubble.iconView, 0,
-                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-        ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
-        if (bubble.iconView != null) {
-            bubble.iconView.setSuppressDot(mSuppressNewDot, false /* animate */);
+
+        if (mBubbleContainer.getChildCount() == 0) {
+            mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
         }
+
+        bubble.inflate(mInflater, this);
+        bubble.getIconView().setBubbleIconFactory(mBubbleIconFactory);
+        bubble.getIconView().updateViews();
+
+        // Set the dot position to the opposite of the side the stack is resting on, since the stack
+        // resting slightly off-screen would result in the dot also being off-screen.
+        bubble.getIconView().setDotPosition(
+                !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+
+        mBubbleContainer.addView(bubble.getIconView(), 0,
+                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+        ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
         animateInFlyoutForBubble(bubble);
         requestUpdate();
         logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
@@ -702,13 +762,14 @@
 
     // via BubbleData.Listener
     void removeBubble(Bubble bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "removeBubble: " + bubble);
         }
         // Remove it from the views
-        int removedIndex = mBubbleContainer.indexOfChild(bubble.iconView);
+        int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView());
         if (removedIndex >= 0) {
             mBubbleContainer.removeViewAt(removedIndex);
+            bubble.cleanupExpandedState();
             logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
         } else {
             Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
@@ -726,8 +787,10 @@
     public void updateBubbleOrder(List<Bubble> bubbles) {
         for (int i = 0; i < bubbles.size(); i++) {
             Bubble bubble = bubbles.get(i);
-            mBubbleContainer.reorderView(bubble.iconView, i);
+            mBubbleContainer.reorderView(bubble.getIconView(), i);
         }
+
+        updateBubbleZOrdersAndDotPosition(false /* animate */);
     }
 
     /**
@@ -737,7 +800,7 @@
      */
     // via BubbleData.Listener
     public void setSelectedBubble(@Nullable Bubble bubbleToSelect) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
         }
         if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
@@ -760,9 +823,8 @@
                 requestUpdate();
                 logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                 logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
-                notifyExpansionChanged(previouslySelected.entry, false /* expanded */);
-                notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.entry,
-                        true /* expanded */);
+                notifyExpansionChanged(previouslySelected, false /* expanded */);
+                notifyExpansionChanged(bubbleToSelect, true /* expanded */);
             });
         }
     }
@@ -774,42 +836,32 @@
      */
     // via BubbleData.Listener
     public void setExpanded(boolean shouldExpand) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "setExpanded: " + shouldExpand);
         }
-        boolean wasExpanded = mIsExpanded;
-        if (shouldExpand == wasExpanded) {
+        if (shouldExpand == mIsExpanded) {
             return;
         }
-        if (wasExpanded) {
-            // Collapse the stack
-            mExpandedViewContainer.setAlpha(0.0f);
-            // TODO: In order to prevent flicker, code below should be executed after the alpha
-            // value set on the mExpandedViewContainer is reflected on the screen. However, we
-            // cannot just postpone the execution like #setSelectedBubble(), since some of member
-            // variables referred by the code are overridden before the execution.
-            if (mExpandedBubble != null) {
-                mExpandedBubble.setContentVisibility(false);
-            }
-            animateExpansion(false /* expand */);
+        if (mIsExpanded) {
+            animateCollapse();
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
         } else {
-            // Expand the stack
-            animateExpansion(true /* expand */);
+            animateExpansion();
             // TODO: move next line to BubbleData
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
             logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
         }
-        notifyExpansionChanged(mExpandedBubble.entry, mIsExpanded);
+        notifyExpansionChanged(mExpandedBubble, mIsExpanded);
     }
 
     /**
      * Dismiss the stack of bubbles.
+     *
      * @deprecated
      */
     @Deprecated
     void stackDismissed(int reason) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "stackDismissed: reason=" + reason);
         }
         mBubbleData.dismissAll(reason);
@@ -826,21 +878,23 @@
         float y = event.getRawY();
         if (mIsExpanded) {
             if (isIntersecting(mBubbleContainer, x, y)) {
+                // Could be tapping or dragging a bubble while expanded
                 for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
                     BubbleView view = (BubbleView) mBubbleContainer.getChildAt(i);
                     if (isIntersecting(view, x, y)) {
                         return view;
                     }
                 }
-            } else if (isIntersecting(mExpandedViewContainer, x, y)) {
-                return mExpandedViewContainer;
             }
-            // Outside parts of view we care about.
+            BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
+            if (bev.intersectingTouchableContent((int) x, (int) y)) {
+                return bev;
+            }
+            // Outside of the parts we care about.
             return null;
         } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
             return mFlyout;
         }
-
         // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
         return this;
     }
@@ -859,7 +913,7 @@
     @Deprecated
     @MainThread
     void collapseStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack()");
         }
         mBubbleData.setExpanded(false);
@@ -871,7 +925,7 @@
     @Deprecated
     @MainThread
     void collapseStack(Runnable endRunnable) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "collapseStack(endRunnable)");
         }
         collapseStack();
@@ -889,73 +943,83 @@
     @Deprecated
     @MainThread
     void expandStack() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "expandStack()");
         }
         mBubbleData.setExpanded(true);
     }
 
-    /**
-     * Tell the stack to animate to collapsed or expanded state.
-     */
-    private void animateExpansion(boolean shouldExpand) {
-        if (DEBUG) {
-            Log.d(TAG, "animateExpansion: shouldExpand=" + shouldExpand);
-        }
-        if (mIsExpanded != shouldExpand) {
-            hideFlyoutImmediate();
-
-            mIsExpanded = shouldExpand;
-            updateExpandedBubble();
-            applyCurrentState();
-
-            mIsExpansionAnimating = true;
-
-            Runnable updateAfter = () -> {
-                applyCurrentState();
-                mIsExpansionAnimating = false;
-                requestUpdate();
-            };
-
-            if (shouldExpand) {
-                mBubbleContainer.setActiveController(mExpandedAnimationController);
-                mExpandedAnimationController.expandFromStack(() -> {
-                    updatePointerPosition();
-                    updateAfter.run();
-                } /* after */);
-            } else {
-                mBubbleContainer.cancelAllAnimations();
-                mExpandedAnimationController.collapseBackToStack(
-                        mStackAnimationController.getStackPositionAlongNearestHorizontalEdge(),
-                        () -> {
-                            mBubbleContainer.setActiveController(mStackAnimationController);
-                            updateAfter.run();
-                        });
-            }
-
-            final float xStart =
-                    mStackAnimationController.getStackPosition().x < getWidth() / 2
-                            ? -mExpandedAnimateXDistance
-                            : mExpandedAnimateXDistance;
-
-            final float yStart = Math.min(
-                    mStackAnimationController.getStackPosition().y,
-                    mExpandedAnimateYDistance);
-            final float yDest = getYPositionForExpandedView();
-
-            if (shouldExpand) {
-                mExpandedViewContainer.setTranslationX(xStart);
-                mExpandedViewContainer.setTranslationY(yStart);
-            }
-
-            mExpandedViewXAnim.animateToFinalPosition(shouldExpand ? 0f : xStart);
-            mExpandedViewYAnim.animateToFinalPosition(shouldExpand ? yDest : yStart);
-        }
+    private void beforeExpandedViewAnimation() {
+        hideFlyoutImmediate();
+        updateExpandedBubble();
+        updateExpandedView();
+        mIsExpansionAnimating = true;
     }
 
-    private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
-        if (mExpandListener != null) {
-            mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+    private void afterExpandedViewAnimation() {
+        updateExpandedView();
+        mIsExpansionAnimating = false;
+        requestUpdate();
+    }
+
+    private void animateCollapse() {
+        mIsExpanded = false;
+        final Bubble previouslySelected = mExpandedBubble;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.cancelAllAnimations();
+        mExpandedAnimationController.collapseBackToStack(
+                mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
+                /* collapseTo */,
+                () -> {
+                    mBubbleContainer.setActiveController(mStackAnimationController);
+                    afterExpandedViewAnimation();
+                    previouslySelected.setContentVisibility(false);
+                });
+
+        mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
+        mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(0f);
+    }
+
+    private void animateExpansion() {
+        mIsExpanded = true;
+        beforeExpandedViewAnimation();
+
+        mBubbleContainer.setActiveController(mExpandedAnimationController);
+        mExpandedAnimationController.expandFromStack(() -> {
+            updatePointerPosition();
+            afterExpandedViewAnimation();
+        } /* after */);
+
+
+        mExpandedViewContainer.setTranslationX(getCollapsedX());
+        mExpandedViewContainer.setTranslationY(getCollapsedY());
+        mExpandedViewContainer.setAlpha(0f);
+
+        mExpandedViewXAnim.animateToFinalPosition(0f);
+        mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
+        mExpandedViewContainer.animate()
+                .setDuration(100)
+                .alpha(1f);
+    }
+
+    private float getCollapsedX() {
+        return mStackAnimationController.getStackPosition().x < getWidth() / 2
+                ? -mExpandedAnimateXDistance
+                : mExpandedAnimateXDistance;
+    }
+
+    private float getCollapsedY() {
+        return Math.min(mStackAnimationController.getStackPosition().y,
+                mExpandedAnimateYDistance);
+    }
+
+    private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
+        if (mExpandListener != null && bubble != null) {
+            mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
         }
     }
 
@@ -968,7 +1032,7 @@
 
     /** Moves the bubbles out of the way if they're going to be over the keyboard. */
     public void onImeVisibilityChanged(boolean visible, int height) {
-        mStackAnimationController.setImeHeight(height + mImeOffset);
+        mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
 
         if (!mIsExpanded) {
             mStackAnimationController.animateForImeVisibility(visible);
@@ -977,7 +1041,7 @@
 
     /** Called when a drag operation on an individual bubble has started. */
     public void onBubbleDragStart(View bubble) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragStart: bubble=" + bubble);
         }
         mExpandedAnimationController.prepareForBubbleDrag(bubble);
@@ -996,7 +1060,7 @@
     /** Called when a drag operation on an individual bubble has finished. */
     public void onBubbleDragFinish(
             View bubble, float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
         }
 
@@ -1005,11 +1069,11 @@
         }
 
         mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
-        springOutDismissTargetAndHideCircle();
+        hideDismissTarget();
     }
 
     void onDragStart() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragStart()");
         }
         if (mIsExpanded || mIsExpansionAnimating) {
@@ -1033,7 +1097,7 @@
     }
 
     void onDragFinish(float x, float y, float velX, float velY) {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "onDragFinish");
         }
 
@@ -1046,8 +1110,8 @@
                 StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
 
         mStackOnLeftOrWillBe = newStackX <= 0;
-        updateBubbleShadowsAndDotPosition(true /* animate */);
-        springOutDismissTargetAndHideCircle();
+        updateBubbleZOrdersAndDotPosition(true /* animate */);
+        hideDismissTarget();
     }
 
     void onFlyoutDragStart() {
@@ -1055,6 +1119,12 @@
     }
 
     void onFlyoutDragged(float deltaX) {
+        // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
+        // is continually called.
+        if (mFlyout.getWidth() <= 0) {
+            return;
+        }
+
         final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
         mFlyoutDragDeltaX = deltaX;
 
@@ -1062,7 +1132,7 @@
                 onLeft ? -deltaX / mFlyout.getWidth() : deltaX / mFlyout.getWidth();
         mFlyout.setCollapsePercent(Math.min(1f, Math.max(0f, collapsePercent)));
 
-        // Calculate how to translate the flyout if it has been dragged too far in etiher direction.
+        // Calculate how to translate the flyout if it has been dragged too far in either direction.
         float overscrollTranslation = 0f;
         if (collapsePercent < 0f || collapsePercent > 1f) {
             // Whether we are more than 100% transitioned to the dot.
@@ -1073,7 +1143,6 @@
             // after it has already become the dot.
             final boolean overscrollingLeft =
                     (onLeft && collapsePercent > 1f) || (!onLeft && collapsePercent < 0f);
-
             overscrollTranslation =
                     (overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
                             * (overscrollingLeft ? -1 : 1)
@@ -1086,6 +1155,19 @@
     }
 
     /**
+     * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
+     * once it collapses.
+     */
+    @Nullable private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
+
+    void onFlyoutTapped() {
+        mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+
+        mFlyout.removeCallbacks(mHideFlyout);
+        mHideFlyout.run();
+    }
+
+    /**
      * Called when the flyout drag has finished, and returns true if the gesture successfully
      * dismissed the flyout.
      */
@@ -1181,9 +1263,6 @@
 
                 animateDesaturateAndDarken(magnetView, true);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(true);
-
         } else {
             mAnimatingMagnet = false;
 
@@ -1194,8 +1273,6 @@
                 mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
                 animateDesaturateAndDarken(magnetView, false);
             }
-
-            mDismissContainer.animateEncircleCenterWithX(false);
         }
 
         mVibrator.vibrate(VibrationEffect.get(toTarget
@@ -1214,7 +1291,7 @@
             mAfterMagnet = null;
 
             mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
-            mDismissContainer.animateEncirclingCircleDisappearance();
+            mDismissContainer.springOut();
 
             // 'Implode' the stack and then hide the dismiss target.
             if (touchedView == this) {
@@ -1252,7 +1329,7 @@
         }
     }
 
-    /** Animates in the dismiss target, including the gradient behind it. */
+    /** Animates in the dismiss target. */
     private void springInDismissTarget() {
         if (mShowingDismiss) {
             return;
@@ -1270,7 +1347,7 @@
      * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
      * were dragged into the target and encircled.
      */
-    private void springOutDismissTargetAndHideCircle() {
+    private void hideDismissTarget() {
         if (!mShowingDismiss) {
             return;
         }
@@ -1287,6 +1364,12 @@
     /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
     private void animateFlyoutCollapsed(boolean collapsed, float velX) {
         final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+        // If the flyout was tapped, we want a higher stiffness for the collapse animation so it's
+        // faster.
+        mFlyoutTransitionSpring.getSpring().setStiffness(
+                (mBubbleToExpandAfterFlyoutCollapse != null)
+                        ? SpringForce.STIFFNESS_MEDIUM
+                        : SpringForce.STIFFNESS_LOW);
         mFlyoutTransitionSpring
                 .setStartValue(mFlyoutDragDeltaX)
                 .setStartVelocity(velX)
@@ -1295,141 +1378,140 @@
                         : 0f);
     }
 
-    /**
-     * Calculates how large the expanded view of the bubble can be. This takes into account the
-     * y position when the bubbles are expanded as well as the bounds of the dismiss target.
-     */
-    int getMaxExpandedHeight() {
-        int expandedY = (int) mExpandedAnimationController.getExpandedY();
-        // PIP dismiss view uses FLAG_LAYOUT_IN_SCREEN so we need to subtract the bottom inset
-        int pipDismissHeight = mPipDismissHeight - getBottomInset();
-        return mDisplaySize.y - expandedY - mBubbleSize - pipDismissHeight;
+    /** Updates the dot visibility, this is used in response to a zen mode config change. */
+    void updateDots() {
+        int bubbsCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbsCount; i++) {
+            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
+            // If nothing changed the animation won't happen
+            bv.updateDotVisibility(true /* animate */);
+        }
     }
 
     /**
      * Calculates the y position of the expanded view when it is expanded.
      */
-    float getYPositionForExpandedView() {
-        return getStatusBarHeight() + mBubbleSize + mBubblePadding + mPointerHeight;
+    float getExpandedViewY() {
+        return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop + mPointerHeight;
     }
 
     /**
-     * Called when the height of the currently expanded view has changed (not via an
-     * update to the bubble's desired height but for some other reason, e.g. permission view
-     * goes away).
-     */
-    void onExpandedHeightChanged() {
-        if (mIsExpanded) {
-            requestUpdate();
-        }
-    }
-
-    /** Sets whether all bubbles in the stack should not show the 'new' dot. */
-    void setSuppressNewDot(boolean suppressNewDot) {
-        mSuppressNewDot = suppressNewDot;
-
-        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
-            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
-            bv.setSuppressDot(suppressNewDot, true /* animate */);
-        }
-    }
-
-    /**
-     * Sets whether the flyout should not appear, even if the notif otherwise would generate one.
-     */
-    void setSuppressFlyout(boolean suppressFlyout) {
-        mSuppressFlyout = suppressFlyout;
-    }
-
-    /**
-     * Callback to run after the flyout hides. Also called if a new flyout is shown before the
-     * previous one animates out.
-     */
-    private Runnable mAfterFlyoutHides;
-
-    /**
      * Animates in the flyout for the given bubble, if available, and then hides it after some time.
      */
     @VisibleForTesting
     void animateInFlyoutForBubble(Bubble bubble) {
-        final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
-
-        // Show the message if one exists, and we're not expanded or animating expansion.
-        if (updateMessage != null
-                && !isExpanded()
-                && !mIsExpansionAnimating
-                && !mIsGestureInProgress
-                && !mSuppressFlyout) {
-            if (bubble.iconView != null) {
-                // Temporarily suppress the dot while the flyout is visible.
-                bubble.iconView.setSuppressDot(
-                        true /* suppressDot */, false /* animate */);
-
-                mFlyoutDragDeltaX = 0f;
-                mFlyout.setAlpha(0f);
-
-                if (mAfterFlyoutHides != null) {
-                    mAfterFlyoutHides.run();
-                }
-
-                mAfterFlyoutHides = () -> {
-                    if (bubble.iconView == null) {
-                        return;
-                    }
-
-                    // If we're going to suppress the dot, make it visible first so it'll
-                    // visibly animate away.
-                    if (mSuppressNewDot) {
-                        bubble.iconView.setSuppressDot(
-                                false /* suppressDot */, false /* animate */);
-                    }
-
-                    // Reset dot suppression. If we're not suppressing due to DND, then
-                    // stop suppressing it with no animation (since the flyout has
-                    // transformed into the dot). If we are suppressing due to DND, animate
-                    // it away.
-                    bubble.iconView.setSuppressDot(
-                            mSuppressNewDot /* suppressDot */,
-                            mSuppressNewDot /* animate */);
-                };
-
-                // Post in case layout isn't complete and getWidth returns 0.
-                post(() -> {
-                    // An auto-expanding bubble could have been posted during the time it takes to
-                    // layout.
-                    if (isExpanded()) {
-                        return;
-                    }
-
-                    mFlyout.showFlyout(
-                            updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
-                            mStackAnimationController.isStackOnLeftSide(),
-                            bubble.iconView.getBadgeColor(), mAfterFlyoutHides);
-                });
-            }
-
-            mFlyout.removeCallbacks(mHideFlyout);
-            mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
-            logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());
+        if (!bubble.showFlyoutForBubble()) {
+            // In case flyout was suppressed for this update, reset now.
+            bubble.setSuppressFlyout(false);
+            return;
         }
+        if (updateMessage == null
+                || isExpanded()
+                || mIsExpansionAnimating
+                || mIsGestureInProgress
+                || mBubbleToExpandAfterFlyoutCollapse != null
+                || bubble.getIconView() == null) {
+            // Skip the message if none exists, we're expanded or animating expansion, or we're
+            // about to expand a bubble from the previous tapped flyout, or if bubble view is null.
+            return;
+        }
+        mFlyoutDragDeltaX = 0f;
+        clearFlyoutOnHide();
+        mFlyoutOnHide = () -> {
+            resetDot(bubble);
+            if (mBubbleToExpandAfterFlyoutCollapse == null) {
+                return;
+            }
+            mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
+            mBubbleData.setExpanded(true);
+            mBubbleToExpandAfterFlyoutCollapse = null;
+        };
+        mFlyout.setVisibility(INVISIBLE);
+
+        // Temporarily suppress the dot while the flyout is visible.
+        bubble.getIconView().setSuppressDot(
+                true /* suppressDot */, false /* animate */);
+
+        // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
+        post(() -> {
+            // An auto-expanding bubble could have been posted during the time it takes to
+            // layout.
+            if (isExpanded()) {
+                return;
+            }
+            final Runnable expandFlyoutAfterDelay = () -> {
+                mAnimateInFlyout = () -> {
+                    mFlyout.setVisibility(VISIBLE);
+                    mFlyoutDragDeltaX =
+                            mStackAnimationController.isStackOnLeftSide()
+                                    ? -mFlyout.getWidth()
+                                    : mFlyout.getWidth();
+                    animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
+                    mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+                };
+                mFlyout.postDelayed(mAnimateInFlyout, 200);
+            };
+            mFlyout.setupFlyoutStartingAsDot(
+                    updateMessage, mStackAnimationController.getStackPosition(), getWidth(),
+                    mStackAnimationController.isStackOnLeftSide(),
+                    bubble.getIconView().getBadgeColor() /* dotColor */,
+                    expandFlyoutAfterDelay /* onLayoutComplete */,
+                    mFlyoutOnHide,
+                    bubble.getIconView().getDotCenter());
+            mFlyout.bringToFront();
+        });
+        mFlyout.removeCallbacks(mHideFlyout);
+        mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+        logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
+    }
+
+    private void resetDot(Bubble bubble) {
+        final boolean suppressDot = !bubble.showBubbleDot();
+        // If we're going to suppress the dot, make it visible first so it'll
+        // visibly animate away.
+
+        if (suppressDot) {
+            bubble.getIconView().setSuppressDot(
+                    false /* suppressDot */, false /* animate */);
+        }
+        // Reset dot suppression. If we're not suppressing due to DND, then
+        // stop suppressing it with no animation (since the flyout has
+        // transformed into the dot). If we are suppressing due to DND, animate
+        // it away.
+        bubble.getIconView().setSuppressDot(
+                suppressDot /* suppressDot */,
+                suppressDot /* animate */);
     }
 
     /** Hide the flyout immediately and cancel any pending hide runnables. */
     private void hideFlyoutImmediate() {
-        if (mAfterFlyoutHides != null) {
-            mAfterFlyoutHides.run();
-        }
-
+        clearFlyoutOnHide();
+        mFlyout.removeCallbacks(mAnimateInFlyout);
         mFlyout.removeCallbacks(mHideFlyout);
         mFlyout.hideFlyout();
     }
 
+    private void clearFlyoutOnHide() {
+        mFlyout.removeCallbacks(mAnimateInFlyout);
+        if (mFlyoutOnHide == null) {
+            return;
+        }
+        mFlyoutOnHide.run();
+        mFlyoutOnHide = null;
+    }
+
     @Override
     public void getBoundsOnScreen(Rect outRect) {
         if (!mIsExpanded) {
             if (mBubbleContainer.getChildCount() > 0) {
                 mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
             }
+            // Increase the touch target size of the bubble
+            outRect.top -= mBubbleTouchPadding;
+            outRect.left -= mBubbleTouchPadding;
+            outRect.right += mBubbleTouchPadding;
+            outRect.bottom += mBubbleTouchPadding;
         } else {
             mBubbleContainer.getBoundsOnScreen(outRect);
         }
@@ -1454,14 +1536,6 @@
         return 0;
     }
 
-    private int getBottomInset() {
-        if (getRootWindowInsets() != null) {
-            WindowInsets insets = getRootWindowInsets();
-            return insets.getSystemWindowInsetBottom();
-        }
-        return 0;
-    }
-
     private boolean isIntersecting(View view, float x, float y) {
         mTempLoc = view.getLocationOnScreen();
         mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
@@ -1479,33 +1553,33 @@
     }
 
     private void updateExpandedBubble() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
         }
         mExpandedViewContainer.removeAllViews();
         if (mExpandedBubble != null && mIsExpanded) {
-            mExpandedViewContainer.addView(mExpandedBubble.expandedView);
-            mExpandedBubble.expandedView.populateExpandedView();
+            mExpandedViewContainer.addView(mExpandedBubble.getExpandedView());
+            mExpandedBubble.getExpandedView().populateExpandedView();
             mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
             mExpandedViewContainer.setAlpha(1.0f);
         }
     }
 
-    private void applyCurrentState() {
-        if (DEBUG) {
-            Log.d(TAG, "applyCurrentState: mIsExpanded=" + mIsExpanded);
+    private void updateExpandedView() {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
+            Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
         }
 
         mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
         if (mIsExpanded) {
             // First update the view so that it calculates a new height (ensuring the y position
             // calculation is correct)
-            mExpandedBubble.expandedView.updateView();
-            final float y = getYPositionForExpandedView();
+            mExpandedBubble.getExpandedView().updateView();
+            final float y = getExpandedViewY();
             if (!mExpandedViewYAnim.isRunning()) {
                 // We're not animating so set the value
                 mExpandedViewContainer.setTranslationY(y);
-                mExpandedBubble.expandedView.updateView();
+                mExpandedBubble.getExpandedView().updateView();
             } else {
                 // We are animating so update the value; there is an end listener on the animator
                 // that will ensure expandedeView.updateView gets called.
@@ -1514,29 +1588,17 @@
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
-        updateBubbleShadowsAndDotPosition(false);
+        updateBubbleZOrdersAndDotPosition(false);
     }
 
     /** Sets the appropriate Z-order and dot position for each bubble in the stack. */
-    private void updateBubbleShadowsAndDotPosition(boolean animate) {
-        int bubbsCount = mBubbleContainer.getChildCount();
-        for (int i = 0; i < bubbsCount; i++) {
+    private void updateBubbleZOrdersAndDotPosition(boolean animate) {
+        int bubbleCount = mBubbleContainer.getChildCount();
+        for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
             bv.updateDotVisibility(true /* animate */);
             bv.setZ((BubbleController.MAX_BUBBLES
                     * getResources().getDimensionPixelSize(R.dimen.bubble_elevation)) - i);
-
-            // Draw the shadow around the circle inscribed within the bubble's bounds. This
-            // (intentionally) does not draw a shadow behind the update dot, which should be drawing
-            // its own shadow since it's on a different (higher) plane.
-            bv.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setOval(0, 0, mBubbleSize, mBubbleSize);
-                }
-            });
-            bv.setClipToOutline(false);
-
             // If the dot is on the left, and so is the stack, we need to change the dot position.
             if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
                 bv.setDotPosition(!mStackOnLeftOrWillBe, animate);
@@ -1545,7 +1607,7 @@
     }
 
     private void updatePointerPosition() {
-        if (DEBUG) {
+        if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updatePointerPosition()");
         }
 
@@ -1557,13 +1619,11 @@
         int index = getBubbleIndex(expandedBubble);
         float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
         float halfBubble = mBubbleSize / 2f;
+        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble;
+        // Padding might be adjusted for insets, so get it directly from the view
+        bubbleCenter -= mExpandedViewContainer.getPaddingLeft();
 
-        // Bubbles live in expanded view container (x includes expanded view padding).
-        // Pointer lives in expanded view, which has padding (x does not include padding).
-        // Remove padding when deriving pointer location from bubbles.
-        float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble - mExpandedViewPadding;
-
-        expandedBubble.expandedView.setPointerPosition(bubbleCenter);
+        expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
     }
 
     /**
@@ -1584,7 +1644,7 @@
         if (bubble == null) {
             return 0;
         }
-        return mBubbleContainer.indexOfChild(bubble.iconView);
+        return mBubbleContainer.indexOfChild(bubble.getIconView());
     }
 
     /**
@@ -1617,8 +1677,8 @@
      * @param action the user interaction enum.
      */
     private void logBubbleEvent(@Nullable Bubble bubble, int action) {
-        if (bubble == null || bubble.entry == null
-                || bubble.entry.notification == null) {
+        if (bubble == null || bubble.getEntry() == null
+                || bubble.getEntry().notification == null) {
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     null /* package name */,
                     null /* notification channel */,
@@ -1630,9 +1690,9 @@
                     getNormalizedYPosition(),
                     false /* unread bubble */,
                     false /* on-going bubble */,
-                    false /* foreground bubble */);
+                    false /* isAppForeground (unused) */);
         } else {
-            StatusBarNotification notification = bubble.entry.notification;
+            StatusBarNotification notification = bubble.getEntry().notification;
             StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                     notification.getPackageName(),
                     notification.getNotification().getChannelId(),
@@ -1642,9 +1702,9 @@
                     action,
                     getNormalizedXPosition(),
                     getNormalizedYPosition(),
-                    bubble.entry.showInShadeWhenBubble(),
-                    bubble.entry.isForegroundService(),
-                    BubbleController.isForegroundApp(mContext, notification.getPackageName()));
+                    bubble.showInShadeWhenBubble(),
+                    bubble.isOngoing(),
+                    false /* isAppForeground (unused) */);
         }
     }
 
@@ -1656,7 +1716,7 @@
         if (!isExpanded()) {
             return false;
         }
-        return mExpandedBubble.expandedView.performBackPressIfNeeded();
+        return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
     }
 
     /** For debugging only */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 8fe8bd3..4240e06 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -45,7 +44,6 @@
      */
     private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
 
-    private static final String TAG = "BubbleTouchHandler";
     /**
      * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
      * towards the center of the screen (where the dismiss target is). This value is the width of
@@ -66,11 +64,10 @@
     private int mTouchSlopSquared;
     private VelocityTracker mVelocityTracker;
 
-    private boolean mInDismissTarget;
-    private Handler mHandler = new Handler();
-
     /** View that was initially touched, when we received the first ACTION_DOWN event. */
     private View mTouchedView;
+    /** Whether the current touched view is in the dismiss target. */
+    private boolean mInDismissTarget;
 
     BubbleTouchHandler(BubbleStackView stackView,
             BubbleData bubbleData, Context context) {
@@ -98,6 +95,15 @@
             return false;
         }
 
+        if (!(mTouchedView instanceof BubbleView)
+                && !(mTouchedView instanceof BubbleStackView)
+                && !(mTouchedView instanceof BubbleFlyoutView)) {
+            // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
+            // of expanded view).
+            resetForNextGesture();
+            return false;
+        }
+
         final boolean isStack = mStack.equals(mTouchedView);
         final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
         final float rawX = event.getRawX();
@@ -193,9 +199,8 @@
                                 }
                             });
                 } else if (isFlyout) {
-                    // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
                     if (!mBubbleData.isExpanded() && !mMovedEnough) {
-                        mBubbleData.setExpanded(true);
+                        mStack.onFlyoutTapped();
                     }
                 } else if (mMovedEnough) {
                     if (isStack) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 6f1ed28..4512aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -19,16 +19,23 @@
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
+import android.util.PathParser;
 import android.widget.FrameLayout;
 
 import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.ShadowGenerator;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,23 +45,25 @@
  * A floating object on the screen that can post message updates.
  */
 public class BubbleView extends FrameLayout {
-    private static final String TAG = "BubbleView";
 
     private static final int DARK_ICON_ALPHA = 180;
     private static final double ICON_MIN_CONTRAST = 4.1;
-    private static final int DEFAULT_BACKGROUND_COLOR =  Color.LTGRAY;
+    private static final int DEFAULT_BACKGROUND_COLOR = Color.LTGRAY;
     // Same value as Launcher3 badge code
     private static final float WHITE_SCRIM_ALPHA = 0.54f;
     private Context mContext;
 
     private BadgedImageView mBadgedImageView;
     private int mBadgeColor;
-    private int mPadding;
     private int mIconInset;
+    private Drawable mUserBadgedAppIcon;
 
-    private boolean mSuppressDot = false;
+    // mBubbleIconFactory cannot be static because it depends on Context.
+    private BubbleIconFactory mBubbleIconFactory;
 
-    private NotificationEntry mEntry;
+    private boolean mSuppressDot;
+
+    private Bubble mBubble;
 
     public BubbleView(Context context) {
         this(context, null);
@@ -71,8 +80,6 @@
     public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mContext = context;
-        // XXX: can this padding just be on the view and we look it up?
-        mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
         mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
     }
 
@@ -88,16 +95,15 @@
     }
 
     /**
-     * Populates this view with a notification.
+     * Populates this view with a bubble.
      * <p>
-     * This should only be called when a new notification is being set on the view, updates to the
-     * current notification should use {@link #update(NotificationEntry)}.
+     * This should only be called when a new bubble is being set on the view, updates to the
+     * current bubble should use {@link #update(Bubble)}.
      *
-     * @param entry the notification to display as a bubble.
+     * @param bubble the bubble to display in this view.
      */
-    public void setNotif(NotificationEntry entry) {
-        mEntry = entry;
-        updateViews();
+    public void setBubble(Bubble bubble) {
+        mBubble = bubble;
     }
 
     /**
@@ -105,7 +111,7 @@
      */
     @Nullable
     public NotificationEntry getEntry() {
-        return mEntry;
+        return mBubble != null ? mBubble.getEntry() : null;
     }
 
     /**
@@ -113,24 +119,35 @@
      */
     @Nullable
     public String getKey() {
-        return (mEntry != null) ? mEntry.key : null;
+        return (mBubble != null) ? mBubble.getKey() : null;
     }
 
     /**
-     * Updates the UI based on the entry, updates badge and animates messages as needed.
+     * Updates the UI based on the bubble, updates badge and animates messages as needed.
      */
-    public void update(NotificationEntry entry) {
-        mEntry = entry;
+    public void update(Bubble bubble) {
+        mBubble = bubble;
         updateViews();
     }
 
     /**
+     * @param factory Factory for creating normalized bubble icons.
+     */
+    public void setBubbleIconFactory(BubbleIconFactory factory) {
+        mBubbleIconFactory = factory;
+    }
+
+    public void setAppIcon(Drawable appIcon) {
+        mUserBadgedAppIcon = appIcon;
+    }
+
+    /**
      * @return the {@link ExpandableNotificationRow} view to display notification content when the
      * bubble is expanded.
      */
     @Nullable
     public ExpandableNotificationRow getRowView() {
-        return (mEntry != null) ? mEntry.getRow() : null;
+        return (mBubble != null) ? mBubble.getEntry().getRow() : null;
     }
 
     /** Changes the dot's visibility to match the bubble view's state. */
@@ -138,7 +155,6 @@
         updateDotVisibility(animate, null /* after */);
     }
 
-
     /**
      * Sets whether or not to hide the dot even if we'd otherwise show it. This is used while the
      * flyout is visible or animating, to hide the dot until the flyout visually transforms into it.
@@ -150,18 +166,23 @@
 
     /** Sets the position of the 'new' dot, animating it out and back in if requested. */
     void setDotPosition(boolean onLeft, boolean animate) {
-        if (animate && onLeft != mBadgedImageView.getDotPosition() && !mSuppressDot) {
+        if (animate && onLeft != mBadgedImageView.getDotOnLeft() && shouldShowDot()) {
             animateDot(false /* showDot */, () -> {
-                mBadgedImageView.setDotPosition(onLeft);
+                mBadgedImageView.setDotOnLeft(onLeft);
                 animateDot(true /* showDot */, null);
             });
         } else {
-            mBadgedImageView.setDotPosition(onLeft);
+            mBadgedImageView.setDotOnLeft(onLeft);
         }
     }
 
+    float[] getDotCenter() {
+        float[] unscaled = mBadgedImageView.getDotCenter();
+        return new float[]{unscaled[0], unscaled[1]};
+    }
+
     boolean getDotPositionOnLeft() {
-        return mBadgedImageView.getDotPosition();
+        return mBadgedImageView.getDotOnLeft();
     }
 
     /**
@@ -169,12 +190,12 @@
      * after animation if requested.
      */
     private void updateDotVisibility(boolean animate, Runnable after) {
-        boolean showDot = getEntry().showInShadeWhenBubble() && !mSuppressDot;
-
+        final boolean showDot = shouldShowDot();
         if (animate) {
             animateDot(showDot, after);
         } else {
             mBadgedImageView.setShowDot(showDot);
+            mBadgedImageView.setDotScale(showDot ? 1f : 0f);
         }
     }
 
@@ -182,62 +203,86 @@
      * Animates the badge to show or hide.
      */
     private void animateDot(boolean showDot, Runnable after) {
-        if (mBadgedImageView.isShowingDot() != showDot) {
-            if (showDot) {
-                mBadgedImageView.setShowDot(true);
-            }
-
-            mBadgedImageView.clearAnimation();
-            mBadgedImageView.animate().setDuration(200)
-                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
-                    .setUpdateListener((valueAnimator) -> {
-                        float fraction = valueAnimator.getAnimatedFraction();
-                        fraction = showDot ? fraction : 1f - fraction;
-                        mBadgedImageView.setDotScale(fraction);
-                    }).withEndAction(() -> {
-                        if (!showDot) {
-                            mBadgedImageView.setShowDot(false);
-                        }
-
-                        if (after != null) {
-                            after.run();
-                        }
-            }).start();
+        if (mBadgedImageView.isShowingDot() == showDot) {
+            return;
         }
+        // Do NOT wait until after animation ends to setShowDot
+        // to avoid overriding more recent showDot states.
+        mBadgedImageView.setShowDot(showDot);
+        mBadgedImageView.clearAnimation();
+        mBadgedImageView.animate().setDuration(200)
+                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                .setUpdateListener((valueAnimator) -> {
+                    float fraction = valueAnimator.getAnimatedFraction();
+                    fraction = showDot ? fraction : 1f - fraction;
+                    mBadgedImageView.setDotScale(fraction);
+                }).withEndAction(() -> {
+            mBadgedImageView.setDotScale(showDot ? 1f : 0f);
+            if (after != null) {
+                after.run();
+            }
+        }).start();
     }
 
     void updateViews() {
-        if (mEntry == null) {
+        if (mBubble == null || mBubbleIconFactory == null) {
             return;
         }
-        Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
-        Notification n = mEntry.notification.getNotification();
-        Icon ic;
-        boolean needsTint;
-        if (metadata != null) {
-            ic = metadata.getIcon();
-            needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
-        } else {
-            needsTint = n.getLargeIcon() == null;
-            ic = needsTint ? n.getSmallIcon() : n.getLargeIcon();
-        }
+        // Update icon.
+        Notification.BubbleMetadata metadata = mBubble.getEntry().getBubbleMetadata();
+        Notification n = mBubble.getEntry().notification.getNotification();
+        Icon ic = metadata.getIcon();
+        boolean needsTint = ic.getType() != Icon.TYPE_ADAPTIVE_BITMAP;
+
         Drawable iconDrawable = ic.loadDrawable(mContext);
         if (needsTint) {
-            mBadgedImageView.setImageDrawable(buildIconWithTint(iconDrawable, n.color));
-        } else {
-            mBadgedImageView.setImageDrawable(iconDrawable);
+            iconDrawable = buildIconWithTint(iconDrawable, n.color);
         }
+        Bitmap bubbleIcon = mBubbleIconFactory.createBadgedIconBitmap(iconDrawable,
+                null /* user */,
+                true /* shrinkNonAdaptiveIcons */).icon;
+
+        // Give it a shadow
+        Bitmap userBadgedBitmap = mBubbleIconFactory.createIconBitmap(mUserBadgedAppIcon,
+                1f, mBubbleIconFactory.getBadgeSize());
+        Canvas c = new Canvas();
+        ShadowGenerator shadowGenerator = new ShadowGenerator(mBubbleIconFactory.getBadgeSize());
+        c.setBitmap(userBadgedBitmap);
+        shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+
+        mBubbleIconFactory.badgeWithDrawable(bubbleIcon,
+                new BitmapDrawable(mContext.getResources(), userBadgedBitmap));
+        mBadgedImageView.setImageBitmap(bubbleIcon);
+
+        // Update badge.
         int badgeColor = determineDominateColor(iconDrawable, n.color);
         mBadgeColor = badgeColor;
         mBadgedImageView.setDotColor(badgeColor);
-        animateDot(mEntry.showInShadeWhenBubble() /* showDot */, null /* after */);
+
+        // Update dot.
+        Path iconPath = PathParser.createPathFromPathData(
+                getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = mBubbleIconFactory.getNormalizer().getScale(iconDrawable,
+                null /* outBounds */, null /* path */, null /* outMaskShape */);
+        float radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        mBadgedImageView.drawDot(iconPath);
+
+        animateDot(shouldShowDot(), null /* after */);
+    }
+
+    boolean shouldShowDot() {
+        return mBubble.showBubbleDot() && !mSuppressDot;
     }
 
     int getBadgeColor() {
         return mBadgeColor;
     }
 
-    private Drawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
+    private AdaptiveIconDrawable buildIconWithTint(Drawable iconDrawable, int backgroundColor) {
         iconDrawable = checkTint(iconDrawable, backgroundColor);
         InsetDrawable foreground = new InsetDrawable(iconDrawable, mIconInset);
         ColorDrawable background = new ColorDrawable(backgroundColor);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 1fa0e12..59d68bc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.view.DisplayCutout;
 import android.view.View;
 import android.view.WindowInsets;
 
@@ -26,10 +29,13 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Set;
 
 /**
@@ -46,24 +52,29 @@
      */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
-    /** How much to scale down bubbles when they're animating in/out. */
-    private static final float ANIMATE_SCALE_PERCENT = 0.5f;
+    /** Duration of the expand/collapse target path animation. */
+    private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
 
-    /** The stack position to collapse back to in {@link #collapseBackToStack}. */
-    private PointF mCollapseToPoint;
+    /** Stiffness for the expand/collapse path-following animation. */
+    private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
+
+    /** What percentage of the screen to use when centering the bubbles in landscape. */
+    private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
 
     /** Horizontal offset between bubbles, which we need to know to re-stack them. */
     private float mStackOffsetPx;
-    /** Spacing between bubbles in the expanded state. */
-    private float mBubblePaddingPx;
+    /** Space between status bar and bubbles in the expanded state. */
+    private float mBubblePaddingTop;
     /** Size of each bubble. */
     private float mBubbleSizePx;
     /** Height of the status bar. */
     private float mStatusBarHeight;
     /** Size of display. */
     private Point mDisplaySize;
-    /** Size of dismiss target at bottom of screen. */
-    private float mPipDismissHeight;
+    /** Max number of bubbles shown in row above expanded view.*/
+    private int mBubblesMaxRendered;
+    /** What the current screen orientation is. */
+    private int mScreenOrientation;
 
     /** Whether the dragged-out bubble is in the dismiss target. */
     private boolean mIndividualBubbleWithinDismissTarget = false;
@@ -86,10 +97,13 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
+    private float mLauncherGridDiff;
 
-    public ExpandedAnimationController(Point displaySize, int expandedViewPadding) {
-        mDisplaySize = displaySize;
+    public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
+            int orientation) {
+        updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
+        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -109,7 +123,7 @@
         mAnimatingExpand = true;
         mAfterExpand = after;
 
-        startOrUpdateExpandAnimation();
+        startOrUpdatePathAnimation(true /* expanding */);
     }
 
     /** Animate collapsing the bubbles back to their stacked position. */
@@ -119,43 +133,105 @@
         mAfterCollapse = after;
         mCollapsePoint = collapsePoint;
 
-        startOrUpdateCollapseAnimation();
+        startOrUpdatePathAnimation(false /* expanding */);
     }
 
-    private void startOrUpdateExpandAnimation() {
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> animation.position(getBubbleLeft(index), getExpandedY()))
-                .startAll(() -> {
-                    mAnimatingExpand = false;
-
-                    if (mAfterExpand != null) {
-                        mAfterExpand.run();
-                    }
-
-                    mAfterExpand = null;
-                });
+    /**
+     * Update effective screen width based on current orientation.
+     * @param orientation Landscape or portrait.
+     * @param displaySize Updated display size.
+     */
+    public void updateOrientation(int orientation, Point displaySize) {
+        mScreenOrientation = orientation;
+        mDisplaySize = displaySize;
+        if (mLayout != null) {
+            Resources res = mLayout.getContext().getResources();
+            mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            mStatusBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height);
+        }
     }
 
-    private void startOrUpdateCollapseAnimation() {
-        // Stack to the left if we're going to the left, or right if not.
-        final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
-        animationsForChildrenFromIndex(
-                0, /* startIndex */
-                (index, animation) -> {
-                    animation.position(
-                            mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx),
-                            mCollapsePoint.y);
-                })
-                .startAll(() -> {
-                    mAnimatingCollapse = false;
+    /**
+     * Animates the bubbles along a curved path, either to expand them along the top or collapse
+     * them back into a stack.
+     */
+    private void startOrUpdatePathAnimation(boolean expanding) {
+        Runnable after;
 
-                    if (mAfterCollapse != null) {
-                        mAfterCollapse.run();
-                    }
+        if (expanding) {
+            after = () -> {
+                mAnimatingExpand = false;
 
-                    mAfterCollapse = null;
-                });
+                if (mAfterExpand != null) {
+                    mAfterExpand.run();
+                }
+
+                mAfterExpand = null;
+            };
+        } else {
+            after = () -> {
+                mAnimatingCollapse = false;
+
+                if (mAfterCollapse != null) {
+                    mAfterCollapse.run();
+                }
+
+                mAfterCollapse = null;
+            };
+        }
+
+        // Animate each bubble individually, since each path will end in a different spot.
+        animationsForChildrenFromIndex(0, (index, animation) -> {
+            final View bubble = mLayout.getChildAt(index);
+
+            // Start a path at the bubble's current position.
+            final Path path = new Path();
+            path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
+
+            final float expandedY = getExpandedY();
+            if (expanding) {
+                // If we're expanding, first draw a line from the bubble's current position to the
+                // top of the screen.
+                path.lineTo(bubble.getTranslationX(), expandedY);
+
+                // Then, draw a line across the screen to the bubble's resting position.
+                path.lineTo(getBubbleLeft(index), expandedY);
+            } else {
+                final float sideMultiplier =
+                        mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x) ? -1 : 1;
+                final float stackedX = mCollapsePoint.x + (sideMultiplier * index * mStackOffsetPx);
+
+                // If we're collapsing, draw a line from the bubble's current position to the side
+                // of the screen where the bubble will be stacked.
+                path.lineTo(stackedX, expandedY);
+
+                // Then, draw a line down to the stack position.
+                path.lineTo(stackedX, mCollapsePoint.y);
+            }
+
+            // The lead bubble should be the bubble with the longest distance to travel when we're
+            // expanding, and the bubble with the shortest distance to travel when we're collapsing.
+            // During expansion from the left side, the last bubble has to travel to the far right
+            // side, so we have it lead and 'pull' the rest of the bubbles into place. From the
+            // right side, the first bubble is traveling to the top left, so it leads. During
+            // collapse to the left, the first bubble has the shortest travel time back to the stack
+            // position, so it leads (and vice versa).
+            final boolean firstBubbleLeads =
+                    (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+                            || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+            final int startDelay = firstBubbleLeads
+                    ? (index * 10)
+                    : ((mLayout.getChildCount() - index) * 10);
+
+            animation
+                    .followAnimatedTargetAlongPath(
+                            path,
+                            EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
+                            Interpolators.LINEAR /* targetAnimInterpolator */)
+                    .withStartDelay(startDelay)
+                    .withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
+        }).startAll(after);
     }
 
     /** Prepares the given bubble to be dragged out. */
@@ -265,6 +341,7 @@
     public void onGestureFinished() {
         mBubbleDraggedOutEnough = false;
         mBubbleDraggingOut = null;
+        updateBubblePositions();
     }
 
     /**
@@ -276,41 +353,38 @@
                 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
     }
 
-    /**
-     * Animates the bubbles, starting at the given index, to the left or right by the given number
-     * of bubble widths. Passing zero for numBubbleWidths will animate the bubbles to their normal
-     * positions.
-     */
-    private void animateStackByBubbleWidthsStartingFrom(int numBubbleWidths, int startIndex) {
-        animationsForChildrenFromIndex(
-                startIndex,
-                (index, animation) ->
-                        animation.translationX(getXForChildAtIndex(index + numBubbleWidths)))
-            .startAll();
-    }
-
     /** The Y value of the row of expanded bubbles. */
     public float getExpandedY() {
         if (mLayout == null || mLayout.getRootWindowInsets() == null) {
             return 0;
         }
         final WindowInsets insets = mLayout.getRootWindowInsets();
-        return mBubblePaddingPx + Math.max(
+        return mBubblePaddingTop + Math.max(
             mStatusBarHeight,
             insets.getDisplayCutout() != null
                 ? insets.getDisplayCutout().getSafeInsetTop()
                 : 0);
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("ExpandedAnimationController state:");
+        pw.print("  isActive:          "); pw.println(isActiveController());
+        pw.print("  animatingExpand:   "); pw.println(mAnimatingExpand);
+        pw.print("  animatingCollapse: "); pw.println(mAnimatingCollapse);
+        pw.print("  bubbleInDismiss:   "); pw.println(mIndividualBubbleWithinDismissTarget);
+        pw.print("  springingBubble:   "); pw.println(mSpringingBubbleToTouch);
+    }
+
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         final Resources res = layout.getResources();
         mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePaddingPx = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mStatusBarHeight =
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
-        mPipDismissHeight = res.getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height);
+        mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
 
         // Ensure that all child views are at 1x scale, and visible, in case they were animating
         // in.
@@ -351,11 +425,11 @@
         // If a bubble is added while the expand/collapse animations are playing, update the
         // animation to include the new bubble.
         if (mAnimatingExpand) {
-            startOrUpdateExpandAnimation();
+            startOrUpdatePathAnimation(true /* expanding */);
         } else if (mAnimatingCollapse) {
-            startOrUpdateCollapseAnimation();
+            startOrUpdatePathAnimation(false /* expanding */);
         } else {
-            child.setTranslationX(getXForChildAtIndex(index));
+            child.setTranslationX(getBubbleLeft(index));
             animationForChild(child)
                     .translationY(
                             getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
@@ -389,6 +463,12 @@
     @Override
     void onChildReordered(View child, int oldIndex, int newIndex) {
         updateBubblePositions();
+
+        // We expect reordering during collapse, since we'll put the last selected bubble on top.
+        // Update the collapse animation so they end up in the right stacked positions.
+        if (mAnimatingCollapse) {
+            startOrUpdatePathAnimation(false /* expanding */);
+        }
     }
 
     private void updateBubblePositions() {
@@ -411,35 +491,100 @@
         }
     }
 
-    /** Returns the appropriate X translation value for a bubble at the given index. */
-    private float getXForChildAtIndex(int index) {
-        return mBubblePaddingPx + (mBubbleSizePx + mBubblePaddingPx) * index;
-    }
-
     /**
      * @param index Bubble index in row.
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSizePx + mBubblePaddingPx);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleFromRowLeft = index * (mBubbleSizePx + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleFromRowLeft;
+    }
+
+    /**
+     * When expanded, the bubbles are centered in the screen. In portrait, all available space is
+     * used. In landscape we have too much space so the value is restricted. This method accounts
+     * for window decorations (nav bar, cutouts).
+     *
+     * @return the desired width to display the expanded bubbles in.
+     */
+    private float getWidthForDisplayingBubbles() {
+        final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */);
+        if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            // display size y in landscape will be the smaller dimension of the screen
+            return Math.max(mDisplaySize.y, availableWidth * CENTER_BUBBLES_LANDSCAPE_PERCENT);
+        } else {
+            return availableWidth;
+        }
+    }
+
+    /**
+     * Determines the available screen width without the cutout.
+     *
+     * @param subtractStableInsets Whether or not stable insets should also be removed from the
+     *                            returned width.
+     * @return the total screen width available accounting for cutouts and insets,
+     * iff {@param includeStableInsets} is true.
+     */
+    private float getAvailableScreenWidth(boolean subtractStableInsets) {
+        float availableSize = mDisplaySize.x;
+        WindowInsets insets = mLayout != null ? mLayout.getRootWindowInsets() : null;
+        if (insets != null) {
+            int cutoutLeft = 0;
+            int cutoutRight = 0;
+            DisplayCutout cutout = insets.getDisplayCutout();
+            if (cutout != null) {
+                cutoutLeft = cutout.getSafeInsetLeft();
+                cutoutRight = cutout.getSafeInsetRight();
+            }
+            final int stableLeft = subtractStableInsets ? insets.getStableInsetLeft() : 0;
+            final int stableRight = subtractStableInsets ? insets.getStableInsetRight() : 0;
+            availableSize -= Math.max(stableLeft, cutoutLeft);
+            availableSize -= Math.max(stableRight, cutoutRight);
+        }
+        return availableSize;
     }
 
     private float getRowLeft() {
         if (mLayout == null) {
             return 0;
         }
+
         int bubbleCount = mLayout.getChildCount();
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSizePx;
-        float gap = (bubbleCount - 1) * mBubblePaddingPx;
-        float row = gap + (float) bubble;
+        final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplaySize.x / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        // This display size we're using includes the size of the insets, we want the true
+        // center of the display minus the notch here, which means we should include the
+        // stable insets (e.g. status bar, nav bar) in this calculation.
+        final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = trueCenter - halfRow;
 
-        return rowLeftFromScreenLeft;
+        return rowLeft;
+    }
+
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        /**
+         * Ordered left to right:
+         *  Screen edge
+         *      [mExpandedViewPadding]
+         *  Expanded view edge
+         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
+         *  Launcher's app icon grid edge that we must match
+         */
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
+
+        final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mBubblesMaxRendered - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 3a33392..563a0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.bubbles.animation;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.content.Context;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -160,7 +167,7 @@
 
         /** Whether this controller is the currently active controller for its associated layout. */
         protected boolean isActiveController() {
-            return this == mLayout.mController;
+            return mLayout != null && this == mLayout.mController;
         }
 
         protected void setLayout(PhysicsAnimationLayout layout) {
@@ -232,7 +239,7 @@
                 }
 
                 if (endActions != null) {
-                    mLayout.setEndActionForMultipleProperties(
+                    setEndActionForMultipleProperties(
                             runAllEndActions,
                             allAnimatedProperties.toArray(
                                     new DynamicAnimation.ViewProperty[0]));
@@ -243,6 +250,44 @@
                 }
             };
         }
+
+        /**
+         * Sets an end action that will be run when all child animations for a given property have
+         * stopped running.
+         */
+        protected void setEndActionForProperty(
+                Runnable action, DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.put(property, action);
+        }
+
+        /**
+         * Sets an end action that will be run when all child animations for all of the given
+         * properties have stopped running.
+         */
+        protected void setEndActionForMultipleProperties(
+                Runnable action, DynamicAnimation.ViewProperty... properties) {
+            final Runnable checkIfAllFinished = () -> {
+                if (!mLayout.arePropertiesAnimating(properties)) {
+                    action.run();
+
+                    for (DynamicAnimation.ViewProperty property : properties) {
+                        removeEndActionForProperty(property);
+                    }
+                }
+            };
+
+            for (DynamicAnimation.ViewProperty property : properties) {
+                setEndActionForProperty(checkIfAllFinished, property);
+            }
+        }
+
+        /**
+         * Removes the end listener that would have been called when all child animations for a
+         * given property stopped running.
+         */
+        protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
+            mLayout.mEndActionForProperty.remove(property);
+        }
     }
 
     /**
@@ -275,43 +320,6 @@
         }
     }
 
-    /**
-     * Sets an end action that will be run when all child animations for a given property have
-     * stopped running.
-     */
-    public void setEndActionForProperty(Runnable action, DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.put(property, action);
-    }
-
-    /**
-     * Sets an end action that will be run when all child animations for all of the given properties
-     * have stopped running.
-     */
-    public void setEndActionForMultipleProperties(
-            Runnable action, DynamicAnimation.ViewProperty... properties) {
-        final Runnable checkIfAllFinished = () -> {
-            if (!arePropertiesAnimating(properties)) {
-                action.run();
-
-                for (DynamicAnimation.ViewProperty property : properties) {
-                    removeEndActionForProperty(property);
-                }
-            }
-        };
-
-        for (DynamicAnimation.ViewProperty property : properties) {
-            setEndActionForProperty(checkIfAllFinished, property);
-        }
-    }
-
-    /**
-     * Removes the end listener that would have been called when all child animations for a given
-     * property stopped running.
-     */
-    public void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
-        mEndActionForProperty.remove(property);
-    }
-
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         addViewInternal(child, index, params, false /* isReorder */);
@@ -372,11 +380,22 @@
     /** Checks whether any animations of the given properties are running on the given view. */
     public boolean arePropertiesAnimatingOnView(
             View view, DynamicAnimation.ViewProperty... properties) {
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
         for (DynamicAnimation.ViewProperty property : properties) {
             final SpringAnimation animation = getAnimationFromView(property, view);
             if (animation != null && animation.isRunning()) {
                 return true;
             }
+
+            // If the target animator is running, its update listener will trigger the translation
+            // physics animations at some point. We should consider the translation properties to be
+            // be animating in this case, even if the physics animations haven't been started yet.
+            final boolean isTranslation =
+                    property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y);
+            if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
+                return true;
+            }
         }
 
         return false;
@@ -388,8 +407,18 @@
             return;
         }
 
+        cancelAllAnimationsOfProperties(
+                mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{}));
+    }
+
+    /** Cancels all animations that are running on all child views, for the given properties. */
+    public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) {
+        if (mController == null) {
+            return;
+        }
+
         for (int i = 0; i < getChildCount(); i++) {
-            for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
+            for (DynamicAnimation.ViewProperty property : properties) {
                 final DynamicAnimation anim = getAnimationAtIndex(property, i);
                 if (anim != null) {
                     anim.cancel();
@@ -400,6 +429,14 @@
 
     /** Cancels all of the physics animations running on the given view. */
     public void cancelAnimationsOnView(View view) {
+        // If present, cancel the target animator so it doesn't restart the translation physics
+        // animations.
+        final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
+        if (targetAnimator != null) {
+            targetAnimator.cancel();
+        }
+
+        // Cancel physics animations on the view.
         for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
             getAnimationFromView(property, view).cancel();
         }
@@ -470,6 +507,11 @@
         return (SpringAnimation) view.getTag(getTagIdForProperty(property));
     }
 
+    /** Retrieves the target animator from the view via the view tag system. */
+    @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
+        return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
+    }
+
     /** Sets up SpringAnimations of the given property for each child view in the layout. */
     private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
         for (int i = 0; i < getChildCount(); i++) {
@@ -587,7 +629,7 @@
          * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
          * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
          */
-        private Runnable[] mPositionEndActions;
+        @Nullable private Runnable[] mPositionEndActions;
 
         /**
          * All of the properties that have been set and will animate when {@link #start} is called.
@@ -603,6 +645,46 @@
         /** The animation controller that last retrieved this animator instance. */
         private PhysicsAnimationController mAssociatedController;
 
+        /**
+         * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
+         * the path is traversed, the view's translation spring animation final positions are
+         * updated such that the view 'follows' the current position on the path.
+         */
+        @Nullable private ObjectAnimator mPathAnimator;
+
+        /** Current position on the path. This is animated by {@link #mPathAnimator}. */
+        private PointF mCurrentPointOnPath = new PointF();
+
+        /**
+         * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
+         * of {@link #mCurrentPointOnPath}.
+         */
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathX") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.x = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.x;
+            }
+        };
+
+        private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
+                new FloatProperty<PhysicsPropertyAnimator>("PathY") {
+            @Override
+            public void setValue(PhysicsPropertyAnimator object, float value) {
+                mCurrentPointOnPath.y = value;
+            }
+
+            @Override
+            public Float get(PhysicsPropertyAnimator object) {
+                return mCurrentPointOnPath.y;
+            }
+        };
+
         protected PhysicsPropertyAnimator(View view) {
             this.mView = view;
         }
@@ -628,6 +710,7 @@
 
         /** Animate the view's translationX value to the provided value. */
         public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
         }
 
@@ -640,6 +723,7 @@
 
         /** Animate the view's translationY value to the provided value. */
         public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
+            mPathAnimator = null; // We aren't using the path anymore if we're translating.
             return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
         }
 
@@ -661,6 +745,46 @@
             return translationY(translationY);
         }
 
+        /**
+         * Animates a 'target' point that moves along the given path, using the provided duration
+         * and interpolator to animate the target. The view itself is animated using physics-based
+         * animations, whose final positions are updated to the target position as it animates. This
+         * results in the view 'following' the target in a realistic way.
+         *
+         * This method will override earlier calls to {@link #translationX}, {@link #translationY},
+         * or {@link #position}, ultimately animating the view's position to the final point on the
+         * given path.
+         *
+         * Any provided end listeners will be called when the physics-based animations kicked off by
+         * the moving target have completed - not when the target animation completes.
+         */
+        public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
+                Path path,
+                int targetAnimDuration,
+                TimeInterpolator targetAnimInterpolator,
+                Runnable... endActions) {
+            mPathAnimator = ObjectAnimator.ofFloat(
+                    this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
+            mPathAnimator.setDuration(targetAnimDuration);
+            mPathAnimator.setInterpolator(targetAnimInterpolator);
+
+            mPositionEndActions = endActions;
+
+            // Remove translation related values since we're going to ignore them and follow the
+            // path instead.
+            clearTranslationValues();
+            return this;
+        }
+
+        private void clearTranslationValues() {
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
+            mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
+            mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
+            mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
+        }
+
         /** Animate the view's scaleX value to the provided value. */
         public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
             return property(DynamicAnimation.SCALE_X, scaleX, endActions);
@@ -742,7 +866,7 @@
             if (after != null && after.length > 0) {
                 final DynamicAnimation.ViewProperty[] propertiesArray =
                         properties.toArray(new DynamicAnimation.ViewProperty[0]);
-                setEndActionForMultipleProperties(() -> {
+                mAssociatedController.setEndActionForMultipleProperties(() -> {
                     for (Runnable callback : after) {
                         callback.run();
                     }
@@ -774,8 +898,20 @@
                         new Runnable[]{waitForBothXAndY});
             }
 
+            if (mPathAnimator != null) {
+                startPathAnimation();
+            }
+
             // Actually start the animations.
             for (DynamicAnimation.ViewProperty property : properties) {
+                // Don't start translation animations if we're using a path animator, the update
+                // listeners added to that animator will take care of that.
+                if (mPathAnimator != null
+                        && (property.equals(DynamicAnimation.TRANSLATION_X)
+                            || property.equals(DynamicAnimation.TRANSLATION_Y))) {
+                    return;
+                }
+
                 if (mInitialPropertyValues.containsKey(property)) {
                     property.setValue(mView, mInitialPropertyValues.get(property));
                 }
@@ -797,7 +933,16 @@
 
         /** Returns the set of properties that will animate once {@link #start} is called. */
         protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
-            return mAnimatedProperties.keySet();
+            final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
+                    mAnimatedProperties.keySet());
+
+            // If we're using a path animator, it'll kick off translation animations.
+            if (mPathAnimator != null) {
+                animatedProperties.add(DynamicAnimation.TRANSLATION_X);
+                animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
+            }
+
+            return animatedProperties;
         }
 
         /**
@@ -812,7 +957,7 @@
                 long startDelay,
                 float stiffness,
                 float dampingRatio,
-                Runnable[] afterCallbacks) {
+                Runnable... afterCallbacks) {
             if (view != null) {
                 final SpringAnimation animation =
                         (SpringAnimation) view.getTag(getTagIdForProperty(property));
@@ -855,6 +1000,92 @@
             }
         }
 
+        /**
+         * Updates the final position of a view's animation, without changing any of the animation's
+         * other settings. Calling this before an initial call to {@link #animateValueForChild} will
+         * work, but result in unknown values for stiffness, etc. and is not recommended.
+         */
+        private void updateValueForChild(
+                DynamicAnimation.ViewProperty property, View view, float position) {
+            if (view != null) {
+                final SpringAnimation animation =
+                        (SpringAnimation) view.getTag(getTagIdForProperty(property));
+                final SpringForce animationSpring = animation.getSpring();
+
+                if (animationSpring == null) {
+                    return;
+                }
+
+                animationSpring.setFinalPosition(position);
+                animation.start();
+            }
+        }
+
+        /**
+         * Configures the path animator to respect the settings passed into the animation builder
+         * and adds update listeners that update the translation physics animations. Then, starts
+         * the path animation.
+         */
+        protected void startPathAnimation() {
+            final SpringForce defaultSpringForceX = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_X, mView);
+            final SpringForce defaultSpringForceY = mController.getSpringForce(
+                    DynamicAnimation.TRANSLATION_Y, mView);
+
+            if (mStartDelay > 0) {
+                mPathAnimator.setStartDelay(mStartDelay);
+            }
+
+            final Runnable updatePhysicsAnims = () -> {
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
+                updateValueForChild(
+                        DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
+            };
+
+            mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
+            mPathAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_X,
+                            mView,
+                            mCurrentPointOnPath.x,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceX.getDampingRatio());
+
+                    animateValueForChild(
+                            DynamicAnimation.TRANSLATION_Y,
+                            mView,
+                            mCurrentPointOnPath.y,
+                            mDefaultStartVelocity,
+                            0 /* startDelay */,
+                            mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
+                            mDampingRatio >= 0
+                                    ? mDampingRatio
+                                    : defaultSpringForceY.getDampingRatio());
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    updatePhysicsAnims.run();
+                }
+            });
+
+            // If there's a target animator saved for the view, make sure it's not running.
+            final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
+            if (targetAnimator != null) {
+                targetAnimator.cancel();
+            }
+
+            mView.setTag(R.id.target_animator_tag, mPathAnimator);
+            mPathAnimator.start();
+        }
+
         private void clearAnimator() {
             mInitialPropertyValues.clear();
             mAnimatedProperties.clear();
@@ -864,6 +1095,8 @@
             mStiffness = -1;
             mDampingRatio = -1;
             mEndActionsForProperty.clear();
+            mPathAnimator = null;
+            mPositionEndActions = null;
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index ab8752e4..2ec09a9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 
+import androidx.annotation.Nullable;
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FlingAnimation;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
@@ -33,6 +34,8 @@
 
 import com.google.android.collect.Sets;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.Set;
 
@@ -53,6 +56,10 @@
     /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */
     private static final int ANIMATE_TRANSLATION_FACTOR = 4;
 
+    /** Values to use for animating bubbles in. */
+    private static final float ANIMATE_IN_STIFFNESS = 1000f;
+    private static final int ANIMATE_IN_START_DELAY = 25;
+
     /**
      * Values to use for the default {@link SpringForce} provided to the physics animation layout.
      */
@@ -92,7 +99,7 @@
     private boolean mStackMovedToStartPosition = false;
 
     /** The most recent position in which the stack was resting on the edge of the screen. */
-    private PointF mRestingStackPosition;
+    @Nullable private PointF mRestingStackPosition;
 
     /** The height of the most recently visible IME. */
     private float mImeHeight = 0f;
@@ -139,14 +146,16 @@
 
     /** Horizontal offset of bubbles in the stack. */
     private float mStackOffset;
-    /** Diameter of the bubbles themselves. */
-    private int mIndividualBubbleSize;
+    /** Diameter of the bubble icon. */
+    private int mBubbleIconBitmapSize;
+    /** Width of the bubble (icon and padding). */
+    private int mBubbleSize;
     /**
      * The amount of space to add between the bubbles and certain UI elements, such as the top of
      * the screen or the IME. This does not apply to the left/right sides of the screen since the
      * stack goes offscreen intentionally.
      */
-    private int mBubblePadding;
+    private int mBubblePaddingTop;
     /** How far offscreen the stack rests. */
     private int mBubbleOffscreen;
     /** How far down the screen the stack starts, when there is no pre-existing location. */
@@ -185,7 +194,7 @@
             return false;
         }
 
-        float stackCenter = mStackPosition.x + mIndividualBubbleSize / 2;
+        float stackCenter = mStackPosition.x + mBubbleIconBitmapSize / 2;
         float screenCenter = mLayout.getWidth() / 2;
         return stackCenter < screenCenter;
     }
@@ -197,18 +206,18 @@
      */
     public void springStack(float destinationX, float destinationY) {
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startXVelocity */,
-                    destinationX);
+                0 /* startXVelocity */,
+                destinationX);
 
         springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y,
-                    new SpringForce()
+                new SpringForce()
                         .setStiffness(SPRING_AFTER_FLING_STIFFNESS)
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
-                    0 /* startYVelocity */,
-                    destinationY);
+                0 /* startYVelocity */,
+                destinationY);
     }
 
     /**
@@ -218,7 +227,7 @@
      * @return The X value that the stack will end up at after the fling/spring.
      */
     public float flingStackThenSpringToEdge(float x, float velX, float velY) {
-        final boolean stackOnLeftSide = x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+        final boolean stackOnLeftSide = x - mBubbleIconBitmapSize / 2 < mLayout.getWidth() / 2;
 
         final boolean stackShouldFlingLeft = stackOnLeftSide
                 ? velX < ESCAPE_VELOCITY
@@ -230,6 +239,12 @@
         final float destinationRelativeX = stackShouldFlingLeft
                 ? stackBounds.left : stackBounds.right;
 
+        // If all bubbles were removed during a drag event, just return the X we would have animated
+        // to if there were still bubbles.
+        if (mLayout == null || mLayout.getChildCount() == 0) {
+            return destinationRelativeX;
+        }
+
         // Minimum velocity required for the stack to make it to the targeted side of the screen,
         // taking friction into account (4.2f is the number that friction scalars are multiplied by
         // in DynamicAnimation.DragForce). This is an estimate - it could possibly be slightly off,
@@ -262,15 +277,6 @@
                         .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
                 /* destination */ null);
 
-        mLayout.setEndActionForMultipleProperties(
-                () -> {
-                    mRestingStackPosition = new PointF();
-                    mRestingStackPosition.set(mStackPosition);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-                    mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
-                },
-                DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
-
         // If we're flinging now, there's no more touch event to catch up to.
         mFirstBubbleSpringingToTouch = false;
         mIsMovingFromFlinging = true;
@@ -304,6 +310,18 @@
         setStackPosition(new PointF(x, y));
     }
 
+    /** Description of current animation controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("StackAnimationController state:");
+        pw.print("  isActive:             "); pw.println(isActiveController());
+        pw.print("  restingStackPos:      ");
+        pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
+        pw.print("  currentStackPos:      "); pw.println(mStackPosition.toString());
+        pw.print("  isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
+        pw.print("  withinDismiss:        "); pw.println(mWithinDismissTarget);
+        pw.print("  firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
+    }
+
     /**
      * Flings the first bubble along the given property's axis, using the provided configuration
      * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
@@ -317,7 +335,7 @@
             SpringForce spring,
             Float finalPosition) {
         Log.d(TAG, String.format("Flinging %s.",
-                        PhysicsAnimationLayout.getReadablePropertyName(property)));
+                PhysicsAnimationLayout.getReadablePropertyName(property)));
 
         StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
         final float currentValue = firstBubbleProperty.getValue(this);
@@ -347,6 +365,9 @@
 
                 .addEndListener((animation, canceled, endValue, endVelocity) -> {
                     if (!canceled) {
+                        mRestingStackPosition = new PointF();
+                        mRestingStackPosition.set(mStackPosition);
+
                         springFirstBubbleWithStackFollowing(property, spring, endVelocity,
                                 finalPosition != null
                                         ? finalPosition
@@ -368,8 +389,8 @@
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
         cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
 
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
     }
 
     /** Save the current IME height so that we know where the stack bounds should be. */
@@ -427,7 +448,7 @@
                                     : 0);
             allowableRegion.right =
                     mLayout.getWidth()
-                            - mIndividualBubbleSize
+                            - mBubbleSize
                             + mBubbleOffscreen
                             - Math.max(
                             insets.getSystemWindowInsetRight(),
@@ -436,7 +457,7 @@
                                     : 0);
 
             allowableRegion.top =
-                    mBubblePadding
+                    mBubblePaddingTop
                             + Math.max(
                             mStatusBarHeight,
                             insets.getDisplayCutout() != null
@@ -444,9 +465,9 @@
                                     : 0);
             allowableRegion.bottom =
                     mLayout.getHeight()
-                            - mIndividualBubbleSize
-                            - mBubblePadding
-                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePadding : 0f)
+                            - mBubbleSize
+                            - mBubblePaddingTop
+                            - (mImeHeight > Float.MIN_VALUE ? mImeHeight + mBubblePaddingTop : 0f)
                             - Math.max(
                             insets.getSystemWindowInsetBottom(),
                             insets.getDisplayCutout() != null
@@ -516,13 +537,19 @@
         mWithinDismissTarget = true;
         mFirstBubbleSpringingToTouch = false;
 
-        animationForChildAtIndex(0)
-                .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
-                .translationY(destY, after)
-                .withPositionStartVelocities(velX, velY)
-                .withStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .start();
+        springFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_X,
+                new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+                velX, mLayout.getWidth() / 2f - mBubbleIconBitmapSize / 2f);
+
+        springFirstBubbleWithStackFollowing(
+                DynamicAnimation.TRANSLATION_Y,
+                new SpringForce()
+                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                        .setStiffness(SpringForce.STIFFNESS_MEDIUM),
+                velY, destY, after);
     }
 
     /**
@@ -550,7 +577,7 @@
      */
     protected void springFirstBubbleWithStackFollowing(
             DynamicAnimation.ViewProperty property, SpringForce spring,
-            float vel, float finalPosition) {
+            float vel, float finalPosition, @Nullable Runnable... after) {
 
         if (mLayout.getChildCount() == 0) {
             return;
@@ -564,6 +591,13 @@
         SpringAnimation springAnimation =
                 new SpringAnimation(this, firstBubbleProperty)
                         .setSpring(spring)
+                        .addEndListener((dynamicAnimation, b, v, v1) -> {
+                            if (after != null) {
+                                for (Runnable callback : after) {
+                                    callback.run();
+                                }
+                            }
+                        })
                         .setStartVelocity(vel);
 
         cancelStackPositionAnimation(property);
@@ -620,13 +654,18 @@
 
     @Override
     void onChildAdded(View child, int index) {
+        // Don't animate additions within the dismiss target.
+        if (mWithinDismissTarget) {
+            return;
+        }
+
         if (mLayout.getChildCount() == 1) {
             // If this is the first child added, position the stack in its starting position.
             moveStackToStartPosition();
         } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
             // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
             // to the back of the stack, it'll be largely invisible so don't bother animating it in.
-            animateInBubble(child);
+            animateInBubble(child, index);
         }
     }
 
@@ -641,24 +680,29 @@
                 .translationX(mStackPosition.x - (-xOffset * ANIMATE_TRANSLATION_FACTOR))
                 .start();
 
+        // If there are other bubbles, pull them into the correct position.
         if (mLayout.getChildCount() > 0) {
             animationForChildAtIndex(0).translationX(mStackPosition.x).start();
         } else {
-            // Set the start position back to the default since we're out of bubbles. New bubbles
-            // will then animate in from the start position.
-            mStackPosition = getDefaultStartPosition();
+            // If there's no other bubbles, and we were in the dismiss target, reset the flag.
+            mWithinDismissTarget = false;
         }
     }
 
     @Override
-    void onChildReordered(View child, int oldIndex, int newIndex) {}
+    void onChildReordered(View child, int oldIndex, int newIndex) {
+        if (isStackPositionSet()) {
+            setStackPosition(mStackPosition);
+        }
+    }
 
     @Override
     void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
         Resources res = layout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mIndividualBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
+        mBubbleIconBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_icon_bitmap_size);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
         mStackStartingVerticalOffset =
                 res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
@@ -666,6 +710,20 @@
                 res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
     }
 
+    /**
+     * Update effective screen width based on current orientation.
+     * @param orientation Landscape or portrait.
+     */
+    public void updateOrientation(int orientation) {
+        if (mLayout != null) {
+            Resources res = mLayout.getContext().getResources();
+            mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+            mStatusBarHeight = res.getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_height);
+        }
+    }
+
+
     /** Moves the stack, without any animation, to the starting position. */
     private void moveStackToStartPosition() {
         // Post to ensure that the layout's width and height have been calculated.
@@ -679,7 +737,7 @@
 
             // Animate in the top bubble now that we're visible.
             if (mLayout.getChildCount() > 0) {
-                animateInBubble(mLayout.getChildAt(0));
+                animateInBubble(mLayout.getChildAt(0), 0 /* index */);
             }
         });
     }
@@ -715,7 +773,9 @@
 
         // If we're not the active controller, we don't want to physically move the bubble views.
         if (isActiveController()) {
-            mLayout.cancelAllAnimations();
+            // Cancel animations that could be moving the views.
+            mLayout.cancelAllAnimationsOfProperties(
+                    DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
             cancelStackPositionAnimations();
 
             // Since we're not using the chained animations, apply the offsets manually.
@@ -742,21 +802,34 @@
     }
 
     /** Animates in the given bubble. */
-    private void animateInBubble(View child) {
+    private void animateInBubble(View child, int index) {
         if (!isActiveController()) {
             return;
         }
 
-        child.setTranslationY(mStackPosition.y);
+        final float xOffset =
+                getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
 
-        float xOffset = getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
+        // Position the new bubble in the correct position, scaled down completely.
+        child.setTranslationX(mStackPosition.x + xOffset * index);
+        child.setTranslationY(mStackPosition.y);
+        child.setScaleX(0f);
+        child.setScaleY(0f);
+
+        // Push the subsequent views out of the way, if there are subsequent views.
+        if (index + 1 < mLayout.getChildCount()) {
+            animationForChildAtIndex(index + 1)
+                    .translationX(mStackPosition.x + xOffset * (index + 1))
+                    .withStiffness(SpringForce.STIFFNESS_LOW)
+                    .start();
+        }
+
+        // Scale in the new bubble, slightly delayed.
         animationForChild(child)
-                .scaleX(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .scaleY(ANIMATE_IN_STARTING_SCALE /* from */, 1f /* to */)
-                .alpha(0f /* from */, 1f /* to */)
-                .translationX(
-                        mStackPosition.x - ANIMATE_TRANSLATION_FACTOR * xOffset /* from */,
-                        mStackPosition.x /* to */)
+                .scaleX(1f)
+                .scaleY(1f)
+                .withStiffness(ANIMATE_IN_STIFFNESS)
+                .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
                 .start();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index fba0d50..20742d6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -43,7 +43,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
 
@@ -154,7 +154,7 @@
 
         updateConfiguration();
         Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mKeyguardUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mKeyguardUpdateCallback);
     }
 
     private void updateConfiguration() {
@@ -556,7 +556,7 @@
         mSensorManager.unregisterListener(mSensorEventListener);
         mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
         Dependency.get(StatusBarStateController.class).removeCallback(mStatusBarStateListener);
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mKeyguardUpdateCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mKeyguardUpdateCallback);
     }
 
     public Uri reportRejectedTouch() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index efac147..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),
-                    KeyguardUpdateMonitor.getInstance(context),
+                    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/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
index e5a54b8..abd41d4 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dependency;
 
 /**
  * Controls removing Keyguard authorization when the phone goes to sleep.
@@ -28,7 +29,7 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
     public DozeAuthRemover(Context context) {
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 86d4a48..4d3dc70 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -29,11 +29,12 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -45,7 +46,7 @@
     /** Creates a DozeMachine with its parts for {@code dozeService}. */
     public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager) {
         Context context = dozeService;
-        SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
+        AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
         DockManager dockManager = Dependency.get(DockManager.class);
         WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
@@ -91,20 +92,21 @@
                 params.getPolicy());
     }
 
-    private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
+    private DozeTriggers createDozeTriggers(Context context, AsyncSensorManager sensorManager,
             DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config,
             DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine,
             DockManager dockManager) {
         boolean allowPulseTriggers = true;
         return new DozeTriggers(context, machine, host, alarmManager, config, params,
-                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager);
+                sensorManager, handler, wakeLock, allowPulseTriggers, dockManager,
+                new ProximitySensor(context, sensorManager));
     }
 
     private DozeMachine.Part createDozeUi(Context context, DozeHost host, WakeLock wakeLock,
             DozeMachine machine, Handler handler, AlarmManager alarmManager,
             DozeParameters params) {
         return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params,
-                KeyguardUpdateMonitor.getInstance(context));
+                Dependency.get(KeyguardUpdateMonitor.class));
     }
 
     public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index c09e284..8fe9f92 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -23,6 +23,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -112,7 +113,7 @@
                 }
                 log("init");
                 if (sRegisterKeyguardCallback) {
-                    KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
+                    Dependency.get(KeyguardUpdateMonitor.class).registerCallback(sKeyguardCallback);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 026a625..dfd83a5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -26,7 +26,6 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
@@ -43,11 +42,10 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.R;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.util.AlarmTimeout;
-import com.android.systemui.util.AsyncSensorManager;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
@@ -62,7 +60,7 @@
 
     private final Context mContext;
     private final AlarmManager mAlarmManager;
-    private final SensorManager mSensorManager;
+    private final AsyncSensorManager mSensorManager;
     private final ContentResolver mResolver;
     private final TriggerSensor mPickupSensor;
     private final DozeParameters mDozeParameters;
@@ -74,13 +72,13 @@
     protected TriggerSensor[] mSensors;
 
     private final Handler mHandler = new Handler();
-    private final ProxSensor mProxSensor;
+    private final ProximitySensor mProximitySensor;
     private long mDebounceFrom;
     private boolean mSettingRegistered;
     private boolean mListening;
     private boolean mPaused;
 
-    public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
+    public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
             DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
             Callback callback, Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
         mContext = context;
@@ -91,6 +89,7 @@
         mWakeLock = wakeLock;
         mProxCallback = proxCallback;
         mResolver = mContext.getContentResolver();
+        mCallback = callback;
 
         boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
         mSensors = new TriggerSensor[] {
@@ -146,8 +145,10 @@
                         false /* touchscreen */, mConfig.getWakeLockScreenDebounce()),
         };
 
-        mProxSensor = new ProxSensor(policy);
-        mCallback = callback;
+        mProximitySensor = new ProximitySensor(context, sensorManager);
+
+        mProximitySensor.register(
+                proximityEvent -> mProxCallback.accept(!proximityEvent.getNear()));
     }
 
     /**
@@ -236,7 +237,15 @@
     }
 
     public void setProxListening(boolean listen) {
-        mProxSensor.setRequested(listen);
+        if (mProximitySensor.isRegistered() && listen) {
+            mProximitySensor.alertListeners();
+        } else {
+            if (listen) {
+                mProximitySensor.resume();
+            } else {
+                mProximitySensor.pause();
+            }
+        }
     }
 
     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@@ -267,115 +276,16 @@
     /** Dump current state */
     public void dump(PrintWriter pw) {
         for (TriggerSensor s : mSensors) {
-            pw.print("  Sensor: "); pw.println(s.toString());
+            pw.println("  Sensor: " + s.toString());
         }
-        pw.print("  ProxSensor: "); pw.println(mProxSensor.toString());
+        pw.println("  ProxSensor: " + mProximitySensor.toString());
     }
 
     /**
-     * @return true if prox is currently far, false if near or null if unknown.
+     * @return true if prox is currently near, false if far or null if unknown.
      */
-    public Boolean isProximityCurrentlyFar() {
-        return mProxSensor.mCurrentlyFar;
-    }
-
-    private class ProxSensor implements SensorEventListener {
-
-        boolean mRequested;
-        boolean mRegistered;
-        Boolean mCurrentlyFar;
-        long mLastNear;
-        final AlarmTimeout mCooldownTimer;
-        final AlwaysOnDisplayPolicy mPolicy;
-        final Sensor mSensor;
-        final boolean mUsingBrightnessSensor;
-
-        public ProxSensor(AlwaysOnDisplayPolicy policy) {
-            mPolicy = policy;
-            mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
-                    "prox_cooldown", mHandler);
-
-            // The default prox sensor can be noisy, so let's use a prox gated brightness sensor
-            // if available.
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            mUsingBrightnessSensor = sensor != null;
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            mSensor = sensor;
-        }
-
-        void setRequested(boolean requested) {
-            if (mRequested == requested) {
-                // Send an update even if we don't re-register.
-                mHandler.post(() -> {
-                    if (mCurrentlyFar != null) {
-                        mProxCallback.accept(mCurrentlyFar);
-                    }
-                });
-                return;
-            }
-            mRequested = requested;
-            updateRegistered();
-        }
-
-        private void updateRegistered() {
-            setRegistered(mRequested && !mCooldownTimer.isScheduled());
-        }
-
-        private void setRegistered(boolean register) {
-            if (mRegistered == register) {
-                return;
-            }
-            if (register) {
-                mRegistered = mSensorManager.registerListener(this, mSensor,
-                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
-            } else {
-                mSensorManager.unregisterListener(this);
-                mRegistered = false;
-                mCurrentlyFar = null;
-            }
-        }
-
-        @Override
-        public void onSensorChanged(android.hardware.SensorEvent event) {
-            if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
-
-            if (mUsingBrightnessSensor) {
-                // The custom brightness sensor is gated by the proximity sensor and will return 0
-                // whenever prox is covered.
-                mCurrentlyFar = event.values[0] > 0;
-            } else {
-                mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
-            }
-            mProxCallback.accept(mCurrentlyFar);
-
-            long now = SystemClock.elapsedRealtime();
-            if (mCurrentlyFar == null) {
-                // Sensor has been unregistered by the proxCallback. Do nothing.
-            } else if (!mCurrentlyFar) {
-                mLastNear = now;
-            } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
-                // If the last near was very recent, we might be using more power for prox
-                // wakeups than we're saving from turning of the screen. Instead, turn it off
-                // for a while.
-                mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
-                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
-                updateRegistered();
-            }
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-
-        @Override
-        public String toString() {
-            return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s,"
-                    + " sensor=%s}", mRegistered, mRequested, mCooldownTimer.isScheduled(),
-                    mCurrentlyFar, mSensor);
-        }
+    public Boolean isProximityCurrentlyNear() {
+        return mProximitySensor.isNear();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 00bfb3f..8eed71c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -24,10 +24,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.metrics.LogMaker;
 import android.os.Handler;
@@ -39,16 +35,16 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
-import java.util.function.IntConsumer;
+import java.util.function.Consumer;
 
 /**
  * Handles triggers for ambient state changes.
@@ -67,20 +63,22 @@
      */
     private static boolean sWakeDisplaySensorState = true;
 
+    private static final int PROXIMITY_TIMEOUT_DELAY_MS = 500;
+
     private final Context mContext;
     private final DozeMachine mMachine;
     private final DozeSensors mDozeSensors;
     private final DozeHost mDozeHost;
     private final AmbientDisplayConfiguration mConfig;
     private final DozeParameters mDozeParameters;
-    private final SensorManager mSensorManager;
-    private final Handler mHandler;
+    private final AsyncSensorManager mSensorManager;
     private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
     private final DockEventListener mDockEventListener = new DockEventListener();
     private final DockManager mDockManager;
+    private final ProximitySensor.ProximityCheck mProxCheck;
 
     private long mNotificationPulseTime;
     private boolean mPulsePending;
@@ -89,15 +87,15 @@
 
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AlarmManager alarmManager, AmbientDisplayConfiguration config,
-            DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
-            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager) {
+            DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
+            WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager,
+            ProximitySensor proximitySensor) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
         mConfig = config;
         mDozeParameters = dozeParameters;
         mSensorManager = sensorManager;
-        mHandler = handler;
         mWakeLock = wakeLock;
         mAllowPulseTriggers = allowPulseTriggers;
         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
@@ -105,6 +103,7 @@
                 dozeParameters.getPolicy());
         mUiModeManager = mContext.getSystemService(UiModeManager.class);
         mDockManager = dockManager;
+        mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler);
     }
 
     private void onNotification(Runnable onPulseSuppressedListener) {
@@ -134,25 +133,27 @@
         }
     }
 
-    private void proximityCheckThenCall(IntConsumer callback,
+    private void proximityCheckThenCall(Consumer<Boolean> callback,
             boolean alreadyPerformedProxCheck,
             int reason) {
-        Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar();
+        Boolean cachedProxNear = mDozeSensors.isProximityCurrentlyNear();
         if (alreadyPerformedProxCheck) {
-            callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
-        } else if (cachedProxFar != null) {
-            callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR);
+            callback.accept(null);
+        } else if (cachedProxNear != null) {
+            callback.accept(cachedProxNear);
         } else {
             final long start = SystemClock.uptimeMillis();
-            new ProximityCheck() {
-                @Override
-                public void onProximityResult(int result) {
-                    final long end = SystemClock.uptimeMillis();
-                    DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
-                            end - start, reason);
-                    callback.accept(result);
-                }
-            }.check();
+            mProxCheck.check(PROXIMITY_TIMEOUT_DELAY_MS, near -> {
+                final long end = SystemClock.uptimeMillis();
+                DozeLog.traceProximityResult(
+                        mContext,
+                        near == null ? false : near,
+                        end - start,
+                        reason);
+                callback.accept(near);
+                mWakeLock.release(TAG);
+            });
+            mWakeLock.acquire(TAG);
         }
     }
 
@@ -178,7 +179,7 @@
             }
         } else {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result != null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -267,7 +268,7 @@
 
         if (wake) {
             proximityCheckThenCall((result) -> {
-                if (result == ProximityCheck.RESULT_NEAR) {
+                if (result !=  null && result) {
                     // In pocket, drop event.
                     return;
                 }
@@ -376,7 +377,7 @@
 
         mPulsePending = true;
         proximityCheckThenCall((result) -> {
-            if (result == ProximityCheck.RESULT_NEAR) {
+            if (result != null && result) {
                 // in pocket, abort pulse
                 DozeLog.tracePulseDropped(mContext, "inPocket");
                 mPulsePending = false;
@@ -412,89 +413,11 @@
         pw.print(" notificationPulseTime=");
         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
 
-        pw.print(" pulsePending="); pw.println(mPulsePending);
+        pw.println(" pulsePending=" + mPulsePending);
         pw.println("DozeSensors:");
         mDozeSensors.dump(pw);
     }
 
-    private abstract class ProximityCheck implements SensorEventListener, Runnable {
-        private static final int TIMEOUT_DELAY_MS = 500;
-
-        protected static final int RESULT_UNKNOWN = 0;
-        protected static final int RESULT_NEAR = 1;
-        protected static final int RESULT_FAR = 2;
-        protected static final int RESULT_NOT_CHECKED = 3;
-
-        private boolean mRegistered;
-        private boolean mFinished;
-        private float mMaxRange;
-
-        protected abstract void onProximityResult(int result);
-
-        public void check() {
-            Preconditions.checkState(!mFinished && !mRegistered);
-            Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
-                    mContext.getString(R.string.doze_brightness_sensor_type));
-            if (sensor == null) {
-                sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
-            }
-            if (sensor == null) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
-                finishWithResult(RESULT_UNKNOWN);
-                return;
-            }
-            mDozeSensors.setDisableSensorsInterferingWithProximity(true);
-
-            mMaxRange = sensor.getMaximumRange();
-            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
-                    mHandler);
-            mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
-            mWakeLock.acquire(TAG);
-            mRegistered = true;
-        }
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (event.values.length == 0) {
-                if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!");
-                finishWithResult(RESULT_UNKNOWN);
-            } else {
-                if (DozeMachine.DEBUG) {
-                    Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
-                }
-                final boolean isNear = event.values[0] < mMaxRange;
-                finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
-            }
-        }
-
-        @Override
-        public void run() {
-            if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
-            finishWithResult(RESULT_UNKNOWN);
-        }
-
-        private void finishWithResult(int result) {
-            if (mFinished) return;
-            boolean wasRegistered = mRegistered;
-            if (mRegistered) {
-                mHandler.removeCallbacks(this);
-                mSensorManager.unregisterListener(this);
-                mDozeSensors.setDisableSensorsInterferingWithProximity(false);
-                mRegistered = false;
-            }
-            onProximityResult(result);
-            if (wasRegistered) {
-                mWakeLock.release(TAG);
-            }
-            mFinished = true;
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            // noop
-        }
-    }
-
     private class TriggerReceiver extends BroadcastReceiver {
         private boolean mRegistered;
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3f598ff..c9c6a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -614,7 +614,7 @@
             mHandler.postDelayed(new Runnable() {
                 @Override
                 public void run() {
-                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler);
+                    mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
                     MetricsLogger.action(mContext,
                             MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 1f3403b..6f8665a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -82,7 +82,7 @@
         mGlobalActions.showDialog(mKeyguardMonitor.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPanelExtension.get());
-        KeyguardUpdateMonitor.getInstance(mContext).requestFaceAuth();
+        Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 7b22a49..2960634 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -111,7 +111,12 @@
             mBitmap = mWallpaperManager.getBitmap();
             mWallpaperManager.forgetLoadedWallpaper();
             if (mBitmap != null) {
-                mSurfaceSize.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+                float scale = (float) mScissor.height() / mBitmap.getHeight();
+                int surfaceHeight = Math.max(mScissor.height(), mBitmap.getHeight());
+                int surfaceWidth = scale > 1f
+                        ? Math.round(mBitmap.getWidth() * scale)
+                        : mBitmap.getWidth();
+                mSurfaceSize.set(0, 0, surfaceWidth, surfaceHeight);
             }
         }
         return mBitmap != null;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 48f32cf..1d2de60 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.StatusBarState;
@@ -72,7 +74,8 @@
  */
 public class KeyguardSliceProvider extends SliceProvider implements
         NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
-        NotificationMediaManager.MediaListener, StatusBarStateController.StateListener {
+        NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
+        SystemUIAppComponentFactory.ContextInitializer {
 
     private static final StyleSpan BOLD_STYLE = new StyleSpan(Typeface.BOLD);
     public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
@@ -93,6 +96,7 @@
     @VisibleForTesting
     static final int ALARM_VISIBILITY_HOURS = 12;
 
+    private static final Object sInstanceLock = new Object();
     private static KeyguardSliceProvider sInstance;
 
     protected final Uri mSliceUri;
@@ -130,6 +134,7 @@
     protected boolean mDozing;
     private int mStatusBarState;
     private boolean mMediaIsVisible;
+    private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
 
     /**
      * Receiver responsible for time ticking and updating the date format.
@@ -310,7 +315,10 @@
 
     @Override
     public boolean onCreateSliceProvider() {
-        synchronized (this) {
+        if (mContextAvailableCallback != null) {
+            mContextAvailableCallback.onContextAvailable(getContext());
+        }
+        synchronized (KeyguardSliceProvider.sInstanceLock) {
             KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
             if (oldInstance != null) {
                 oldInstance.onDestroy();
@@ -335,7 +343,7 @@
 
     @VisibleForTesting
     protected void onDestroy() {
-        synchronized (this) {
+        synchronized (KeyguardSliceProvider.sInstanceLock) {
             mNextAlarmController.removeCallback(this);
             mZenModeController.removeCallback(this);
             mMediaWakeLock.setAcquired(false);
@@ -345,6 +353,7 @@
                 getKeyguardUpdateMonitor().removeCallback(mKeyguardUpdateMonitorCallback);
                 getContext().unregisterReceiver(mIntentReceiver);
             }
+            KeyguardSliceProvider.sInstance = null;
         }
     }
 
@@ -449,9 +458,8 @@
         updateNextAlarm();
     }
 
-    @VisibleForTesting
-    protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
-        return KeyguardUpdateMonitor.getInstance(getContext());
+    private KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+        return Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     /**
@@ -530,4 +538,10 @@
             notifyChange();
         }
     }
+
+    @Override
+    public void setContextAvailableCallback(
+            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+        mContextAvailableCallback = callback;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4016b59..d2a9c75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -82,6 +82,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
@@ -98,6 +99,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
+import javax.inject.Inject;
+
 /**
  * Mediates requests related to the keyguard.  This includes queries about the
  * state of the keyguard, power management events that effect whether the keyguard
@@ -215,6 +218,7 @@
     private boolean mBootSendUserPresent;
     private boolean mShuttingDown;
     private boolean mDozing;
+    private final FalsingManager mFalsingManager;
 
     /** High level access to the power manager for WakeLocks */
     private PowerManager mPM;
@@ -677,6 +681,13 @@
         }
     };
 
+    @Inject
+    public KeyguardViewMediator(FalsingManager falsingManager) {
+        super();
+
+        mFalsingManager = falsingManager;
+    }
+
     public void userActivity() {
         mPM.userActivity(SystemClock.uptimeMillis(), false);
     }
@@ -710,15 +721,14 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         mLockPatternUtils = new LockPatternUtils(mContext);
         KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
 
         // Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
         // is disabled.
-        if (mContext.getResources().getBoolean(
-                com.android.keyguard.R.bool.config_enableKeyguardService)) {
+        if (mContext.getResources().getBoolean(R.bool.config_enableKeyguardService)) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
                             KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
@@ -846,7 +856,7 @@
                 playSounds(true);
             }
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedGoingToSleep(why);
+        mUpdateMonitor.dispatchStartedGoingToSleep(why);
         notifyStartedGoingToSleep();
     }
 
@@ -891,7 +901,7 @@
             }
 
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
+        mUpdateMonitor.dispatchFinishedGoingToSleep(why);
     }
 
     private long getLockTimeout(int userId) {
@@ -1004,7 +1014,7 @@
             if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedWakingUp();
+        mUpdateMonitor.dispatchStartedWakingUp();
         maybeSendUserPresentBroadcast();
         Trace.endSection();
     }
@@ -1047,7 +1057,7 @@
      * if there is a secure lock pattern.
      */
     public void onDreamingStarted() {
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStarted();
+        mUpdateMonitor.dispatchDreamingStarted();
         synchronized (this) {
             if (mDeviceInteractive
                     && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
@@ -1060,7 +1070,7 @@
      * A dream stopped.
      */
     public void onDreamingStopped() {
-        KeyguardUpdateMonitor.getInstance(mContext).dispatchDreamingStopped();
+        mUpdateMonitor.dispatchDreamingStopped();
         synchronized (this) {
             if (mDeviceInteractive) {
                 cancelDoKeyguardLaterLocked();
@@ -1456,11 +1466,11 @@
 
     public boolean isSecure(int userId) {
         return mLockPatternUtils.isSecure(userId)
-                || KeyguardUpdateMonitor.getInstance(mContext).isSimPinSecure();
+                || mUpdateMonitor.isSimPinSecure();
     }
 
     public void setSwitchingUser(boolean switching) {
-        KeyguardUpdateMonitor.getInstance(mContext).setSwitchingUser(switching);
+        mUpdateMonitor.setSwitchingUser(switching);
     }
 
     /**
@@ -1603,7 +1613,7 @@
                     Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
                     StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
                     handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
-                    Dependency.get(FalsingManager.class).onSucccessfulUnlock();
+                    mFalsingManager.onSucccessfulUnlock();
                     Trace.endSection();
                     break;
                 case KEYGUARD_DONE_PENDING_TIMEOUT:
@@ -2075,11 +2085,10 @@
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
             ViewGroup container, NotificationPanelView panelView,
             BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
-            View notificationContainer, KeyguardBypassController bypassController,
-            FalsingManager falsingManager) {
+            View notificationContainer, KeyguardBypassController bypassController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
                 biometricUnlockController, mDismissCallbackRegistry, lockIconContainer,
-                notificationContainer, bypassController, falsingManager);
+                notificationContainer, bypassController, mFalsingManager);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
new file mode 100644
index 0000000..6795bff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.pip;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+import android.util.TypedValue;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.policy.PipSnapAlgorithm;
+
+import java.io.PrintWriter;
+
+/**
+ * Handles bounds calculation for PIP on Phone and other form factors, it keeps tracking variant
+ * state changes originated from Window Manager and is the source of truth for PiP window bounds.
+ */
+public class PipBoundsHandler {
+
+    private static final String TAG = PipBoundsHandler.class.getSimpleName();
+    private static final float INVALID_SNAP_FRACTION = -1f;
+
+    private final Context mContext;
+    private final IWindowManager mWindowManager;
+    private final PipSnapAlgorithm mSnapAlgorithm;
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final Rect mStableInsets = new Rect();
+    private final Rect mTmpInsets = new Rect();
+    private final Point mTmpDisplaySize = new Point();
+
+    private IPinnedStackController mPinnedStackController;
+    private ComponentName mLastPipComponentName;
+    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
+
+    private float mDefaultAspectRatio;
+    private float mMinAspectRatio;
+    private float mMaxAspectRatio;
+    private float mAspectRatio;
+    private int mDefaultStackGravity;
+    private int mDefaultMinSize;
+    private Point mScreenEdgeInsets;
+    private int mCurrentMinSize;
+
+    private boolean mIsMinimized;
+    private boolean mIsImeShowing;
+    private int mImeHeight;
+    private boolean mIsShelfShowing;
+    private int mShelfHeight;
+
+    public PipBoundsHandler(Context context) {
+        mContext = context;
+        mSnapAlgorithm = new PipSnapAlgorithm(context);
+        mWindowManager = WindowManagerGlobal.getWindowManagerService();
+        reloadResources();
+        // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
+        // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
+        // triggers a configuration change and the resources to be reloaded.
+        mAspectRatio = mDefaultAspectRatio;
+    }
+
+    /**
+     * TODO: move the resources to SysUI package.
+     */
+    private void reloadResources() {
+        final Resources res = mContext.getResources();
+        mDefaultAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
+        mDefaultStackGravity = res.getInteger(
+                com.android.internal.R.integer.config_defaultPictureInPictureGravity);
+        mDefaultMinSize = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
+        mCurrentMinSize = mDefaultMinSize;
+        final String screenEdgeInsetsDpString = res.getString(
+                com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+        mMinAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
+        mMaxAspectRatio = res.getFloat(
+                com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
+    }
+
+    public void setPinnedStackController(IPinnedStackController controller) {
+        mPinnedStackController = controller;
+    }
+
+    public void setMinEdgeSize(int minEdgeSize) {
+        mCurrentMinSize = minEdgeSize;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on IME visibility change.
+     */
+    public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+        mIsImeShowing = imeVisible;
+        mImeHeight = imeHeight;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on shelf visibility change.
+     */
+    public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        mIsShelfShowing = shelfVisible;
+        mShelfHeight = shelfHeight;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on minimized state change.
+     */
+    public void onMinimizedStateChanged(boolean minimized) {
+        mIsMinimized = minimized;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on movement bounds change.
+     * Note that both inset and normal bounds will be calculated here rather than in the caller.
+     */
+    public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+            Rect animatingBounds, DisplayInfo displayInfo) {
+        getInsetBounds(insetBounds);
+        final Rect defaultBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
+        normalBounds.set(defaultBounds);
+        if (animatingBounds.isEmpty()) {
+            animatingBounds.set(defaultBounds);
+        }
+        if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+            transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                    false /* useCurrentMinEdgeSize */);
+        }
+        displayInfo.copyFrom(mDisplayInfo);
+    }
+
+    /**
+     * Responds to IPinnedStackListener on saving reentry snap fraction
+     * for a given {@link ComponentName}.
+     */
+    public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+        mReentrySnapFraction = getSnapFraction(bounds);
+        mLastPipComponentName = componentName;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on resetting reentry snap fraction
+     * for a given {@link ComponentName}.
+     */
+    public void onResetReentrySnapFraction(ComponentName componentName) {
+        if (componentName.equals(mLastPipComponentName)) {
+            onResetReentrySnapFractionUnchecked();
+        }
+    }
+
+    private void onResetReentrySnapFractionUnchecked() {
+        mReentrySnapFraction = INVALID_SNAP_FRACTION;
+        mLastPipComponentName = null;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on {@link DisplayInfo} change.
+     * It will normally follow up with a
+     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
+     */
+    public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+    }
+
+    /**
+     * Responds to IPinnedStackListener on configuration change.
+     */
+    public void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    /**
+     * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+     * It will normally follow up with a
+     * {@link #onMovementBoundsChanged(Rect, Rect, Rect, DisplayInfo)} callback.
+     */
+    public void onAspectRatioChanged(float aspectRatio) {
+        mAspectRatio = aspectRatio;
+    }
+
+    /**
+     * Responds to IPinnedStackListener on preparing the pinned stack animation.
+     */
+    public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
+        final Rect destinationBounds;
+        if (bounds == null) {
+            destinationBounds = getDefaultBounds(mReentrySnapFraction);
+        } else {
+            destinationBounds = new Rect(bounds);
+        }
+        if (isValidPictureInPictureAspectRatio(aspectRatio)) {
+            transformBoundsToAspectRatio(destinationBounds, aspectRatio,
+                    false /* useCurrentMinEdgeSize */);
+        }
+        if (destinationBounds.equals(bounds)) {
+            return;
+        }
+        mAspectRatio = aspectRatio;
+        onResetReentrySnapFractionUnchecked();
+        try {
+            mPinnedStackController.startAnimation(destinationBounds, sourceRectHint,
+                    -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start PiP animation from SysUI", e);
+        }
+    }
+
+    /**
+     * @return whether the given {@param aspectRatio} is valid.
+     */
+    private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
+        return Float.compare(mMinAspectRatio, aspectRatio) <= 0
+                && Float.compare(aspectRatio, mMaxAspectRatio) <= 0;
+    }
+
+    /**
+     * Set the current bounds (or the default bounds if there are no current bounds) with the
+     * specified aspect ratio.
+     */
+    private void transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
+            boolean useCurrentMinEdgeSize) {
+        // Save the snap fraction, calculate the aspect ratio based on screen size
+        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
+                getMovementBounds(stackBounds));
+
+        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
+        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
+                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
+        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
+        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
+        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
+        if (mIsMinimized) {
+            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
+        }
+    }
+
+    /**
+     * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
+     * will apply the default bounds to the provided snap fraction.
+     */
+    private Rect getDefaultBounds(float snapFraction) {
+        final Rect insetBounds = new Rect();
+        getInsetBounds(insetBounds);
+
+        final Rect defaultBounds = new Rect();
+        final Size size = mSnapAlgorithm.getSizeForAspectRatio(mDefaultAspectRatio,
+                mDefaultMinSize, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        if (snapFraction != INVALID_SNAP_FRACTION) {
+            defaultBounds.set(0, 0, size.getWidth(), size.getHeight());
+            final Rect movementBounds = getMovementBounds(defaultBounds);
+            mSnapAlgorithm.applySnapFraction(defaultBounds, movementBounds, snapFraction);
+        } else {
+            Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds,
+                    0, Math.max(mIsImeShowing ? mImeHeight : 0,
+                            mIsShelfShowing ? mShelfHeight : 0),
+                    defaultBounds);
+        }
+        return defaultBounds;
+    }
+
+    /**
+     * Populates the bounds on the screen that the PIP can be visible in.
+     */
+    private void getInsetBounds(Rect outRect) {
+        try {
+            mWindowManager.getStableInsets(mContext.getDisplayId(), mTmpInsets);
+            outRect.set(mTmpInsets.left + mScreenEdgeInsets.x,
+                    mTmpInsets.top + mScreenEdgeInsets.y,
+                    mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
+                    mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets from WM", e);
+        }
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds) {
+        return getMovementBounds(stackBounds, true /* adjustForIme */);
+    }
+
+    /**
+     * @return the movement bounds for the given {@param stackBounds} and the current state of the
+     *         controller.
+     */
+    private Rect getMovementBounds(Rect stackBounds, boolean adjustForIme) {
+        final Rect movementBounds = new Rect();
+        getInsetBounds(movementBounds);
+
+        // Apply the movement bounds adjustments based on the current state.
+        mSnapAlgorithm.getMovementBounds(stackBounds, movementBounds, movementBounds,
+                (adjustForIme && mIsImeShowing) ? mImeHeight : 0);
+        return movementBounds;
+    }
+
+    /**
+     * Applies the minimized offsets to the given stack bounds.
+     */
+    private void applyMinimizedOffset(Rect stackBounds, Rect movementBounds) {
+        mTmpDisplaySize.set(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
+        try {
+            mWindowManager.getStableInsets(mContext.getDisplayId(), mStableInsets);
+            mSnapAlgorithm.applyMinimizedOffset(stackBounds, movementBounds, mTmpDisplaySize,
+                    mStableInsets);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets from WM", e);
+        }
+    }
+
+    /**
+     * @return the default snap fraction to apply instead of the default gravity when calculating
+     *         the default stack bounds when first entering PiP.
+     */
+    private float getSnapFraction(Rect stackBounds) {
+        return mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds));
+    }
+
+    /**
+     * @return the pixels for a given dp value.
+     */
+    private int dpToPx(float dpValue, DisplayMetrics dm) {
+        return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+    }
+
+    /**
+     * Dumps internal states.
+     */
+    public void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + TAG);
+        pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
+        pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
+        pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
+        pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
+        pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
+        pw.println(innerPrefix + "mMaxAspectRatio=" + mMaxAspectRatio);
+        pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
+        pw.println(innerPrefix + "mDefaultStackGravity=" + mDefaultStackGravity);
+        pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized);
+        pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+        pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+        pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+        pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+        mSnapAlgorithm.dump(pw, innerPrefix);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 1740290..8dfae32 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IActivityManager;
@@ -29,14 +32,16 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.Pair;
+import android.view.DisplayInfo;
 import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -55,8 +60,12 @@
     private IActivityTaskManager mActivityTaskManager;
     private Handler mHandler = new Handler();
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
 
+    private PipBoundsHandler mPipBoundsHandler;
     private InputConsumerController mInputConsumerController;
     private PipMenuActivityController mMenuController;
     private PipMediaController mMediaController;
@@ -116,11 +125,11 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onListenerRegistered(IPinnedStackController controller) {
             mHandler.post(() -> {
+                mPipBoundsHandler.setPinnedStackController(controller);
                 mTouchHandler.setPinnedStackController(controller);
             });
         }
@@ -128,6 +137,7 @@
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onImeVisibilityChanged(imeVisible, imeHeight);
                 mTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
             });
         }
@@ -135,31 +145,66 @@
         @Override
         public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
             mHandler.post(() -> {
-               mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mPipBoundsHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
+                mTouchHandler.onShelfVisibilityChanged(shelfVisible, shelfHeight);
             });
         }
 
         @Override
         public void onMinimizedStateChanged(boolean isMinimized) {
             mHandler.post(() -> {
+                mPipBoundsHandler.onMinimizedStateChanged(isMinimized);
                 mTouchHandler.setMinimizedState(isMinimized, true /* fromController */);
             });
         }
 
         @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds,
-                        fromImeAdjustment, fromShelfAdjustment, displayRotation);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
+                        mTmpDisplayInfo.rotation);
             });
         }
 
         @Override
         public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> mMenuController.setAppActions(actions));
+        }
+
+        @Override
+        public void onSaveReentrySnapFraction(ComponentName componentName, Rect bounds) {
+            mHandler.post(() -> mPipBoundsHandler.onSaveReentrySnapFraction(componentName, bounds));
+        }
+
+        @Override
+        public void onResetReentrySnapFraction(ComponentName componentName) {
+            mHandler.post(() -> mPipBoundsHandler.onResetReentrySnapFraction(componentName));
+        }
+
+        @Override
+        public void onDisplayInfoChanged(DisplayInfo displayInfo) {
+            mHandler.post(() -> mPipBoundsHandler.onDisplayInfoChanged(displayInfo));
+        }
+
+        @Override
+        public void onConfigurationChanged() {
+            mHandler.post(() -> mPipBoundsHandler.onConfigurationChanged());
+        }
+
+        @Override
+        public void onAspectRatioChanged(float aspectRatio) {
+            mHandler.post(() -> mPipBoundsHandler.onAspectRatioChanged(aspectRatio));
+        }
+
+        @Override
+        public void onPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect bounds) {
             mHandler.post(() -> {
-                mMenuController.setAppActions(actions);
+                mPipBoundsHandler.onPrepareAnimation(sourceRectHint, aspectRatio, bounds);
             });
         }
     }
@@ -181,15 +226,27 @@
         }
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
 
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mInputConsumerController = InputConsumerController.getPipInputConsumer();
-        mInputConsumerController.registerInputConsumer();
         mMediaController = new PipMediaController(context, mActivityManager);
         mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
                 mInputConsumerController);
         mTouchHandler = new PipTouchHandler(context, mActivityManager, mActivityTaskManager,
-                mMenuController, mInputConsumerController);
+                mMenuController, mInputConsumerController, mPipBoundsHandler);
         mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
                 mTouchHandler.getMotionHelper());
+
+        // If SystemUI restart, and it already existed a pinned stack,
+        // register the pip input consumer to ensure touch can send to it.
+        try {
+            ActivityManager.StackInfo stackInfo = mActivityTaskManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+            if (stackInfo != null) {
+                mInputConsumerController.registerInputConsumer();
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
     }
 
     /**
@@ -238,5 +295,6 @@
         mInputConsumerController.dump(pw, innerPrefix);
         mMenuController.dump(pw, innerPrefix);
         mTouchHandler.dump(pw, innerPrefix);
+        mPipBoundsHandler.dump(pw, innerPrefix);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 21f5812..4f2a6d8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -48,7 +48,6 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Color;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -64,7 +63,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityManager;
@@ -92,6 +90,7 @@
     public static final int MESSAGE_UPDATE_ACTIONS = 4;
     public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
     public static final int MESSAGE_ANIMATION_ENDED = 6;
+    public static final int MESSAGE_TOUCH_EVENT = 7;
 
     private static final int INITIAL_DISMISS_DELAY = 3500;
     private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -128,10 +127,6 @@
                 }
             };
 
-    private PipTouchState mTouchState;
-    private PointF mDownPosition = new PointF();
-    private PointF mDownDelta = new PointF();
-    private ViewConfiguration mViewConfig;
     private Handler mHandler = new Handler();
     private Messenger mToControllerMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
@@ -169,6 +164,12 @@
                     mAllowTouches = true;
                     break;
                 }
+
+                case MESSAGE_TOUCH_EVENT: {
+                    final MotionEvent ev = (MotionEvent) msg.obj;
+                    dispatchTouchEvent(ev);
+                    break;
+                }
             }
         }
     });
@@ -184,15 +185,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         // Set the flags to allow us to watch for outside touches and also hide the menu and start
         // manipulating the PIP in the same touch gesture
-        mViewConfig = ViewConfiguration.get(this);
-        mTouchState = new PipTouchState(mViewConfig, mHandler, () -> {
-            if (mMenuState == MENU_STATE_CLOSE) {
-                showPipMenu();
-            } else {
-                expandPip();
-            }
-        });
-        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
+        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.pip_menu_activity);
@@ -204,32 +197,6 @@
         mViewRoot.setBackground(mBackgroundDrawable);
         mMenuContainer = findViewById(R.id.menu_container);
         mMenuContainer.setAlpha(0);
-        mMenuContainer.setOnTouchListener((v, event) -> {
-            mTouchState.onTouchEvent(event);
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_UP:
-                    if (mTouchState.isDoubleTap() || mMenuState == MENU_STATE_FULL) {
-                        // Expand to fullscreen if this is a double tap or we are already expanded
-                        expandPip();
-                    } else if (!mTouchState.isWaitingForDoubleTap()) {
-                        // User has stalled long enough for this not to be a drag or a double tap,
-                        // just expand the menu if necessary
-                        if (mMenuState == MENU_STATE_CLOSE) {
-                            showPipMenu();
-                        }
-                    } else {
-                        // Next touch event _may_ be the second tap for the double-tap, schedule a
-                        // fallback runnable to trigger the menu if no touch event occurs before the
-                        // next tap
-                        mTouchState.scheduleDoubleTapTimeoutCallback();
-                    }
-                    // Fall through
-                case MotionEvent.ACTION_CANCEL:
-                    mTouchState.reset();
-                    break;
-            }
-            return true;
-        });
         mSettingsButton = findViewById(R.id.settings);
         mSettingsButton.setAlpha(0);
         mSettingsButton.setOnClickListener((v) -> {
@@ -240,7 +207,11 @@
         mDismissButton = findViewById(R.id.dismiss);
         mDismissButton.setAlpha(0);
         mDismissButton.setOnClickListener(v -> dismissPip());
-        findViewById(R.id.expand_button).setOnClickListener(v -> expandPip());
+        findViewById(R.id.expand_button).setOnClickListener(v -> {
+            if (mMenuContainer.getAlpha() != 0) {
+                expandPip();
+            }
+        });
         mActionsGroup = findViewById(R.id.actions_group);
         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
                 R.dimen.pip_between_action_padding_land);
@@ -298,27 +269,14 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (!mAllowTouches) {
-            return super.dispatchTouchEvent(ev);
+            return false;
         }
 
         // On the first action outside the window, hide the menu
         switch (ev.getAction()) {
             case MotionEvent.ACTION_OUTSIDE:
                 hideMenu();
-                break;
-            case MotionEvent.ACTION_DOWN:
-                mDownPosition.set(ev.getX(), ev.getY());
-                mDownDelta.set(0f, 0f);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
-                if (mDownDelta.length() > mViewConfig.getScaledTouchSlop()
-                        && mMenuState != MENU_STATE_NONE) {
-                    // Restore the input consumer and let that drive the movement of this menu
-                    notifyRegisterInputConsumer();
-                    cancelDelayedFinish();
-                }
-                break;
+                return true;
         }
         return super.dispatchTouchEvent(ev);
     }
@@ -381,7 +339,6 @@
             if (allowMenuTimeout) {
                 repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
             }
-            notifyUnregisterInputConsumer();
         }
     }
 
@@ -506,11 +463,13 @@
                     actionView.setContentDescription(action.getContentDescription());
                     if (action.isEnabled()) {
                         actionView.setOnClickListener(v -> {
-                            try {
-                                action.getActionIntent().send();
-                            } catch (CanceledException e) {
-                                Log.w(TAG, "Failed to send action", e);
-                            }
+                            mHandler.post(() -> {
+                                try {
+                                    action.getActionIntent().send();
+                                } catch (CanceledException e) {
+                                    Log.w(TAG, "Failed to send action", e);
+                                }
+                            });
                         });
                     }
                     actionView.setEnabled(action.isEnabled());
@@ -554,18 +513,6 @@
         mBackgroundDrawable.setAlpha(alpha);
     }
 
-    private void notifyRegisterInputConsumer() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER;
-        sendMessage(m, "Could not notify controller to register input consumer");
-    }
-
-    private void notifyUnregisterInputConsumer() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_UNREGISTER_INPUT_CONSUMER;
-        sendMessage(m, "Could not notify controller to unregister input consumer");
-    }
-
     private void notifyMenuStateChange(int menuState) {
         mMenuState = menuState;
         Message m = Message.obtain();
@@ -583,11 +530,6 @@
         }, false /* notifyMenuVisibility */, false /* isDismissing */);
     }
 
-    private void minimizePip() {
-        sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
-                "Could not notify controller to minimize PIP");
-    }
-
     private void dismissPip() {
         // Do not notify menu visibility when hiding the menu, the controller will do this when it
         // handles the message
@@ -597,12 +539,6 @@
         }, false /* notifyMenuVisibility */, true /* isDismissing */);
     }
 
-    private void showPipMenu() {
-        Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
-        sendMessage(m, "Could not notify controller to show PIP menu");
-    }
-
     private void showSettings() {
         final Pair<ComponentName, Integer> topPipActivityInfo =
                 PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 14459d6..62c59e5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -37,6 +37,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.systemui.pip.phone.PipMediaController.ActionListener;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -156,14 +157,6 @@
                     mListeners.forEach(l -> l.onPipShowMenu());
                     break;
                 }
-                case MESSAGE_REGISTER_INPUT_CONSUMER: {
-                    mInputConsumerController.registerInputConsumer();
-                    break;
-                }
-                case MESSAGE_UNREGISTER_INPUT_CONSUMER: {
-                    mInputConsumerController.unregisterInputConsumer();
-                    break;
-                }
                 case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
                     mToActivityMessenger = msg.replyTo;
                     setStartActivityRequested(false);
@@ -212,15 +205,12 @@
     }
 
     public void onActivityPinned() {
-        if (mMenuState == MENU_STATE_NONE) {
-            // If the menu is not visible, then re-register the input consumer if it is not already
-            // registered
-            mInputConsumerController.registerInputConsumer();
-        }
+        mInputConsumerController.registerInputConsumer();
     }
 
     public void onActivityUnpinned() {
         hideMenu();
+        mInputConsumerController.unregisterInputConsumer();
         setStartActivityRequested(false);
     }
 
@@ -495,11 +485,7 @@
             Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
                     + " menuState=" + menuState + " resize=" + resize);
         }
-        if (menuState == MENU_STATE_NONE) {
-            mInputConsumerController.registerInputConsumer();
-        } else {
-            mInputConsumerController.unregisterInputConsumer();
-        }
+
         if (menuState != mMenuState) {
             mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize));
             if (menuState == MENU_STATE_FULL) {
@@ -521,6 +507,22 @@
         mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0;
     }
 
+    /**
+     * Handles touch event sent from pip input consumer.
+     */
+    void handleTouchEvent(MotionEvent ev) {
+        if (mToActivityMessenger != null) {
+            Message m = Message.obtain();
+            m.what = PipMenuActivity.MESSAGE_TOUCH_EVENT;
+            m.obj = ev;
+            try {
+                mToActivityMessenger.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not dispatch touch event", e);
+            }
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 9c65994..fa60477 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -551,9 +551,8 @@
                         return true;
                     }
 
-                    mActivityTaskManager.resizeStack(stackInfo.stackId, toBounds,
-                            false /* allowResizeInDockedMode */, true /* preserveWindows */,
-                            true /* animate */, duration);
+                    mActivityTaskManager.animateResizePinnedStack(stackInfo.stackId, toBounds,
+                            duration);
                     mBounds.set(toBounds);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b05058a..1f36d97 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -48,6 +48,7 @@
 import com.android.internal.os.logging.MetricsLoggerWrapper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.systemui.R;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
@@ -75,6 +76,7 @@
     private final IActivityTaskManager mActivityTaskManager;
     private final ViewConfiguration mViewConfig;
     private final PipMenuListener mMenuListener = new PipMenuListener();
+    private final PipBoundsHandler mPipBoundsHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
@@ -178,7 +180,8 @@
 
     public PipTouchHandler(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
-            InputConsumerController inputConsumerController) {
+            InputConsumerController inputConsumerController,
+            PipBoundsHandler pipBoundsHandler) {
 
         // Initialize the Pip input consumer
         mContext = context;
@@ -211,6 +214,8 @@
         inputConsumerController.setInputListener(this::handleTouchEvent);
         inputConsumerController.setRegistrationListener(this::onRegistrationChanged);
         onRegistrationChanged(inputConsumerController.isRegistered());
+
+        mPipBoundsHandler = pipBoundsHandler;
     }
 
     public void setTouchEnabled(boolean enabled) {
@@ -363,6 +368,8 @@
         // Update the touch state
         mTouchState.onTouchEvent(ev);
 
+        boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN: {
                 mMotionHelper.synchronizePinnedStackBounds();
@@ -378,6 +385,8 @@
                         break;
                     }
                 }
+
+                shouldDeliverToMenu = !mTouchState.isDragging();
                 break;
             }
             case MotionEvent.ACTION_UP: {
@@ -394,6 +403,7 @@
                 // Fall through to clean up
             }
             case MotionEvent.ACTION_CANCEL: {
+                shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
                 mTouchState.reset();
                 break;
             }
@@ -425,7 +435,20 @@
                 break;
             }
         }
-        return mMenuState == MENU_STATE_NONE;
+
+        // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+        if (shouldDeliverToMenu) {
+            final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+            // Send the cancel event and cancel menu timeout if it starts to drag.
+            if (mTouchState.startedDragging()) {
+                cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+                mMenuController.pokeMenu();
+            }
+
+            mMenuController.handleTouchEvent(cloneEvent);
+        }
+
+        return true;
     }
 
     /**
@@ -741,11 +764,11 @@
                 mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
                         null /* animatorListener */);
                 setMinimizedStateInternal(false);
+            } else if (mTouchState.isDoubleTap()) {
+                // Expand to fullscreen if this is a double tap
+                mMotionHelper.expandPip();
             } else if (mMenuState != MENU_STATE_FULL) {
-                if (mTouchState.isDoubleTap()) {
-                    // Expand to fullscreen if this is a double tap
-                    mMotionHelper.expandPip();
-                } else if (!mTouchState.isWaitingForDoubleTap()) {
+                if (!mTouchState.isWaitingForDoubleTap()) {
                     // User has stalled long enough for this not to be a drag or a double tap, just
                     // expand the menu
                     mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
@@ -756,9 +779,6 @@
                     // next tap
                     mTouchState.scheduleDoubleTapTimeoutCallback();
                 }
-            } else {
-                mMenuController.hideMenu();
-                mMotionHelper.expandPip();
             }
             return true;
         }
@@ -772,14 +792,8 @@
         mMovementBounds = isMenuExpanded
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
-        try {
-            if (mPinnedStackController != null) {
-                mPinnedStackController.setMinEdgeSize(
-                        isMenuExpanded ? mExpandedShortestEdgeSize : 0);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not set minimized state", e);
-        }
+        mPipBoundsHandler.setMinEdgeSize(
+                isMenuExpanded ? mExpandedShortestEdgeSize : 0);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 69efbc8..e3f65ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -106,6 +106,7 @@
                 mIsDoubleTap = !mPreviouslyDragging &&
                         (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
                 mIsWaitingForDoubleTap = false;
+                mIsDragging = false;
                 mLastDownTouchTime = mDownTouchTime;
                 if (mDoubleTapTimeoutCallback != null) {
                     mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 5f2614e..81d6973 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,13 +19,10 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.Display.DEFAULT_DISPLAY;
 
-import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityTaskManager;
-import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -46,16 +43,15 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
+import android.view.DisplayInfo;
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.pip.PipBoundsHandler;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
@@ -111,9 +107,8 @@
     private int mSuspendPipResizingReason;
 
     private Context mContext;
-    private IActivityManager mActivityManager;
+    private PipBoundsHandler mPipBoundsHandler;
     private IActivityTaskManager mActivityTaskManager;
-    private IWindowManager mWindowManager;
     private MediaSessionManager mMediaSessionManager;
     private int mState = STATE_NO_PIP;
     private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
@@ -135,11 +130,16 @@
     private PipNotification mPipNotification;
     private ParceledListSlice mCustomActions;
 
+    // Used to calculate the movement bounds
+    private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+    private final Rect mTmpInsetBounds = new Rect();
+    private final Rect mTmpNormalBounds = new Rect();
+
     // Keeps track of the IME visibility to adjust the PiP when the IME is visible
     private boolean mImeVisible;
     private int mImeHeightAdjustment;
 
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
+    private final PinnedStackListener mPinnedStackListener = new PipManagerPinnedStackListener();
 
     private final Runnable mResizePinnedStackRunnable = new Runnable() {
         @Override
@@ -181,11 +181,7 @@
     /**
      * Handler for messages from the PIP controller.
      */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {}
-
+    private class PipManagerPinnedStackListener extends PinnedStackListener {
         @Override
         public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
             if (mState == STATE_PIP) {
@@ -205,17 +201,13 @@
         }
 
         @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {}
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {}
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
+        public void onMovementBoundsChanged(Rect animatingBounds, boolean fromImeAdjustment,
+                boolean fromShelfAdjustment) {
             mHandler.post(() -> {
-                mDefaultPipBounds.set(normalBounds);
+                // Populate the inset / normal bounds and DisplayInfo from mPipBoundsHandler first.
+                mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
+                        animatingBounds, mTmpDisplayInfo);
+                mDefaultPipBounds.set(animatingBounds);
             });
         }
 
@@ -241,10 +233,8 @@
         }
         mInitialized = true;
         mContext = context;
-
-        mActivityManager = ActivityManager.getService();
+        mPipBoundsHandler = new PipBoundsHandler(context);
         mActivityTaskManager = ActivityTaskManager.getService();
-        mWindowManager = WindowManagerGlobal.getWindowManagerService();
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
@@ -291,7 +281,7 @@
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
 
         try {
-            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+            WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
@@ -441,8 +431,8 @@
         }
         try {
             int animationDurationMs = -1;
-            mActivityTaskManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
-                    true, true, true, animationDurationMs);
+            mActivityTaskManager.animateResizePinnedStack(mPinnedStackId, mCurrentPipBounds,
+                    animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 41f66f7..9221b68 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -155,7 +155,7 @@
         } else if (MOVE_FULL_ROWS.equals(key)) {
             mFullRows = TunerService.parseIntegerSwitch(newValue, true);
         } else if (QuickQSPanel.NUM_QUICK_TILES.equals(key)) {
-            mNumQuickTiles = mQuickQsPanel.getNumQuickTiles(mQs.getContext());
+            mNumQuickTiles = QuickQSPanel.parseNumTiles(newValue);
             clearAnimationState();
         }
         updateAnimators();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
index 8c21dde..55ae61d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
@@ -16,17 +16,20 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Dependency.BG_HANDLER;
+import static com.android.systemui.Dependency.BG_HANDLER_NAME;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.Handler;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -52,9 +55,11 @@
      */
     private static final int SIM_SLOTS = 3;
     private final NetworkController mNetworkController;
+    private final Handler mBgHandler;
 
     private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
     private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+    private TextView mNoSimTextView;
     private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS];
     private CarrierTextController mCarrierTextController;
     private ActivityStarter mActivityStarter;
@@ -63,17 +68,20 @@
 
     @Inject
     public QSCarrierGroup(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
-            NetworkController networkController, ActivityStarter activityStarter) {
+            NetworkController networkController, ActivityStarter activityStarter,
+            @Named(BG_HANDLER_NAME) Handler handler) {
         super(context, attrs);
         mNetworkController = networkController;
         mActivityStarter = activityStarter;
+        mBgHandler = handler;
     }
 
     @VisibleForTesting
     public QSCarrierGroup(Context context, AttributeSet attrs) {
         this(context, attrs,
                 Dependency.get(NetworkController.class),
-                Dependency.get(ActivityStarter.class));
+                Dependency.get(ActivityStarter.class),
+                Dependency.get(BG_HANDLER));
     }
 
     @Override
@@ -94,10 +102,13 @@
         mCarrierDividers[0] = findViewById(R.id.qs_carrier_divider1);
         mCarrierDividers[1] = findViewById(R.id.qs_carrier_divider2);
 
+        mNoSimTextView = findViewById(R.id.no_carrier_text);
+
         for (int i = 0; i < SIM_SLOTS; i++) {
             mInfos[i] = new CellSignalState();
             mCarrierGroups[i].setOnClickListener(this);
         }
+        mNoSimTextView.setOnClickListener(this);
 
         CharSequence separator = mContext.getString(
                 com.android.internal.R.string.kg_text_message_separator);
@@ -110,8 +121,7 @@
             return;
         }
         mListening = listening;
-        // TODO(b/140053526)
-        whitelistIpcs(this::updateListeners);
+        mBgHandler.post(this::updateListeners);
     }
 
     @Override
@@ -155,50 +165,47 @@
 
     @Override
     public void updateCarrierInfo(CarrierTextController.CarrierTextCallbackInfo info) {
-        if (info.airplaneMode) {
-            setVisibility(View.GONE);
-        } else {
-            setVisibility(View.VISIBLE);
-            if (info.anySimReady) {
-                boolean[] slotSeen = new boolean[SIM_SLOTS];
-                if (info.listOfCarriers.length == info.subscriptionIds.length) {
-                    for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
-                        int slot = getSlotIndex(info.subscriptionIds[i]);
-                        if (slot >= SIM_SLOTS) {
-                            Log.w(TAG, "updateInfoCarrier - slot: " + slot);
-                            continue;
-                        }
-                        if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                            Log.e(TAG,
-                                    "Invalid SIM slot index for subscription: "
-                                            + info.subscriptionIds[i]);
-                            continue;
-                        }
-                        mInfos[slot].visible = true;
-                        slotSeen[slot] = true;
-                        mCarrierGroups[slot].setCarrierText(
-                                info.listOfCarriers[i].toString().trim());
-                        mCarrierGroups[slot].setVisibility(View.VISIBLE);
+        mNoSimTextView.setVisibility(View.GONE);
+        if (!info.airplaneMode && info.anySimReady) {
+            boolean[] slotSeen = new boolean[SIM_SLOTS];
+            if (info.listOfCarriers.length == info.subscriptionIds.length) {
+                for (int i = 0; i < SIM_SLOTS && i < info.listOfCarriers.length; i++) {
+                    int slot = getSlotIndex(info.subscriptionIds[i]);
+                    if (slot >= SIM_SLOTS) {
+                        Log.w(TAG, "updateInfoCarrier - slot: " + slot);
+                        continue;
                     }
-                    for (int i = 0; i < SIM_SLOTS; i++) {
-                        if (!slotSeen[i]) {
-                            mInfos[i].visible = false;
-                            mCarrierGroups[i].setVisibility(View.GONE);
-                        }
+                    if (slot == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+                        Log.e(TAG,
+                                "Invalid SIM slot index for subscription: "
+                                        + info.subscriptionIds[i]);
+                        continue;
                     }
-                } else {
-                    Log.e(TAG, "Carrier information arrays not of same length");
+                    mInfos[slot].visible = true;
+                    slotSeen[slot] = true;
+                    mCarrierGroups[slot].setCarrierText(
+                            info.listOfCarriers[i].toString().trim());
+                    mCarrierGroups[slot].setVisibility(View.VISIBLE);
+                }
+                for (int i = 0; i < SIM_SLOTS; i++) {
+                    if (!slotSeen[i]) {
+                        mInfos[i].visible = false;
+                        mCarrierGroups[i].setVisibility(View.GONE);
+                    }
                 }
             } else {
-                mInfos[0].visible = false;
-                mCarrierGroups[0].setCarrierText(info.carrierText);
-                mCarrierGroups[0].setVisibility(View.VISIBLE);
-                for (int i = 1; i < SIM_SLOTS; i++) {
-                    mInfos[i].visible = false;
-                    mCarrierGroups[i].setCarrierText("");
-                    mCarrierGroups[i].setVisibility(View.GONE);
-                }
+                Log.e(TAG, "Carrier information arrays not of same length");
             }
+        } else {
+            // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
+            // info.carrierText in a different view.
+            for (int i = 0; i < SIM_SLOTS; i++) {
+                mInfos[i].visible = false;
+                mCarrierGroups[i].setCarrierText("");
+                mCarrierGroups[i].setVisibility(View.GONE);
+            }
+            mNoSimTextView.setText(info.carrierText);
+            mNoSimTextView.setVisibility(View.VISIBLE);
         }
         handleUpdateState();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
new file mode 100644
index 0000000..3f0c5bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSColorController.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.plugins.qs.QSTile
+
+private const val TAG = "QSColorController"
+private const val QS_COLOR_ICON = "qs_color_icon"
+private const val QS_COLOR_ENABLED = "qs_color_enabled"
+private const val QS_COLOR_OVERRIDDEN_TILES = "qs_color_overridden_tiles"
+private val qsColorIconUri = Settings.System.getUriFor(QS_COLOR_ICON)
+private val qsColorEnabledUri = Settings.System.getUriFor(QS_COLOR_ENABLED)
+class QSColorController private constructor() {
+
+    private var overrideColor = false
+    private var colorIcon = false
+    private lateinit var colorCache: SettingBackedMap
+
+    companion object {
+        val instance = QSColorController()
+        internal fun overrideColor() = instance.overrideColor
+        internal fun colorIcon() = instance.colorIcon
+
+        @QSTile.ColorTile
+        private fun getColorFromSetting(setting: String): Int {
+            return when (setting.toLowerCase()) {
+                "red" -> QSTile.COLOR_TILE_RED
+                "blue" -> QSTile.COLOR_TILE_BLUE
+                "green" -> QSTile.COLOR_TILE_GREEN
+                "yellow" -> QSTile.COLOR_TILE_YELLOW
+                else -> QSTile.COLOR_TILE_ACCENT
+            }
+        }
+    }
+
+    private fun getBooleanSetting(key: String, default: Boolean = false): Boolean =
+            try {
+                Settings.System.getInt(contentResolver, key) != 0
+            } catch (_: Settings.SettingNotFoundException) {
+                default
+            }
+
+    private lateinit var tileHost: QSHost
+    private lateinit var contentResolver: ContentResolver
+
+    fun initQSTileHost(host: QSHost) {
+        tileHost = host
+        contentResolver = tileHost.context.contentResolver
+        colorCache = SettingBackedMap(contentResolver, mutableMapOf())
+        colorIcon = getBooleanSetting(QS_COLOR_ICON)
+        overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
+        readExistingSettings()
+        contentResolver.registerContentObserver(qsColorEnabledUri, true, settingsListener)
+        contentResolver.registerContentObserver(qsColorIconUri, false, settingsListener)
+    }
+
+    private fun readExistingSettings() {
+        Settings.System.getString(contentResolver, QS_COLOR_OVERRIDDEN_TILES)?.split(",")
+                ?.mapNotNull { spec ->
+            Settings.System.getString(contentResolver, "$QS_COLOR_ENABLED/$spec")?.let {
+                spec to it
+            }
+        }?.forEach {
+            modifyTileColor(it.first, getColorFromSetting(it.second))
+        }
+    }
+
+    private val settingsListener = object : ContentObserver(Handler(Looper.getMainLooper())) {
+        override fun onChange(selfChange: Boolean, uri: Uri) {
+            super.onChange(selfChange, uri)
+            when (uri) {
+                qsColorIconUri -> colorIcon = getBooleanSetting(QS_COLOR_ICON)
+                qsColorEnabledUri -> overrideColor = getBooleanSetting(QS_COLOR_ENABLED)
+                else -> {
+                    uri.path?.drop("/system/".length)?.let {
+                        val color = getColorFromSetting(
+                                Settings.System.getString(contentResolver, it) ?: "accent")
+                        val tileSpec = uri.lastPathSegment ?: ""
+                        modifyTileColor(tileSpec, color)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun modifyTileColor(spec: String, @QSTile.ColorTile color: Int) {
+        Log.w(TAG, "Setting color of tile $spec to $color")
+        colorCache.put(spec, color)
+        tileHost.tiles.firstOrNull { it.tileSpec == spec }?.setColor(color)
+    }
+
+    fun applyColorToTile(tile: QSTile) {
+        colorCache.get(tile.tileSpec)?.let {
+            modifyTileColor(tile.tileSpec, it)
+        }
+    }
+
+    fun applyColorToAllTiles() = tileHost.tiles.forEach(::applyColorToTile)
+
+    fun destroy() {
+        contentResolver.unregisterContentObserver(settingsListener)
+    }
+
+    class SettingBackedMap(
+        private val contentResolver: ContentResolver,
+        private val map: MutableMap<String, Int>
+    ) : MutableMap<String, @QSTile.ColorTile Int> by map {
+        override fun put(key: String, @QSTile.ColorTile value: Int): Int? {
+            return map.put(key, value).also {
+                Settings.System.putString(contentResolver, QS_COLOR_OVERRIDDEN_TILES,
+                        map.filterValues { it != QSTile.COLOR_TILE_ACCENT }
+                                .keys
+                                .joinToString(","))
+            }
+        }
+    }
+}
+fun overrideColor() = QSColorController.overrideColor()
+fun colorIcon() = QSColorController.colorIcon()
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index b597a72..0a2533a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -29,6 +29,11 @@
     void setQSPanel(@Nullable QSPanel panel);
 
     /**
+     * Sets the given {@link QuickQSPanel} to be the one associated with quick settings.
+     */
+    default void setQQSPanel(@Nullable QuickQSPanel panel) {};
+
+    /**
      * Sets whether or not the footer should be visible.
      *
      * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE} or {@link View#GONE}.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 644664e..0134aa3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -81,6 +81,7 @@
 
     private boolean mQsDisabled;
     private QSPanel mQsPanel;
+    private QuickQSPanel mQuickQsPanel;
 
     private boolean mExpanded;
 
@@ -177,7 +178,8 @@
     }
 
     private void updateAnimator(int width) {
-        int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
+        int numTiles = mQuickQsPanel != null ? mQuickQsPanel.getNumQuickTiles()
+                : QuickQSPanel.getDefaultMaxTiles();
         int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
                 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
         int remaining = (width - numTiles * size) / (numTiles - 1);
@@ -346,6 +348,11 @@
     }
 
     @Override
+    public void setQQSPanel(@Nullable QuickQSPanel panel) {
+        mQuickQsPanel = panel;
+    }
+
+    @Override
     public void onClick(View v) {
         // Don't do anything until view are unhidden
         if (!mExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index b27bf32..1e763cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -88,6 +88,8 @@
     private int mCurrentUser;
     private StatusBar mStatusBar;
 
+    private QSColorController mQSColorController = QSColorController.Companion.getInstance();
+
     @Inject
     public QSTileHost(Context context,
             StatusBarIconController iconController,
@@ -119,6 +121,8 @@
             // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
             mAutoTiles = autoTiles.get();
         });
+
+        mQSColorController.initQSTileHost(this);
     }
 
     public StatusBarIconController getIconController() {
@@ -132,6 +136,8 @@
         mServices.destroy();
         mPluginManager.removePluginListener(this);
         mDumpController.unregisterDumpable(this);
+
+        mQSColorController.destroy();
     }
 
     @Override
@@ -216,7 +222,7 @@
         if (!TILES_SETTING.equals(key)) {
             return;
         }
-        if (DEBUG) Log.d(TAG, "Recreating tiles");
+        Log.d(TAG, "Recreating tiles");
         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
         }
@@ -225,7 +231,7 @@
         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
                 tile -> {
-                    if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
+                    Log.d(TAG, "Destroying tile: " + tile.getKey());
                     tile.getValue().destroy();
                 });
         final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
@@ -242,9 +248,10 @@
                     newTiles.put(tileSpec, tile);
                 } else {
                     tile.destroy();
+                    Log.d(TAG, "Destroying not available tile: " + tileSpec);
                 }
             } else {
-                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
+                Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
                     tile = createTile(tileSpec);
                     if (tile != null) {
@@ -253,6 +260,7 @@
                             newTiles.put(tileSpec, tile);
                         } else {
                             tile.destroy();
+                            Log.d(TAG, "Destroying not available tile: " + tileSpec);
                         }
                     }
                 } catch (Throwable t) {
@@ -268,13 +276,15 @@
         mTiles.putAll(newTiles);
         if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
             // If we didn't manage to create any tiles, set it to empty (default)
-            if (DEBUG) Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
+            Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
             changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
         } else {
             for (int i = 0; i < mCallbacks.size(); i++) {
                 mCallbacks.get(i).onTilesChanged();
             }
         }
+
+        mQSColorController.applyColorToAllTiles();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 37113cf..85aafa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
 
 import android.content.Context;
@@ -50,9 +49,10 @@
 
     public static final String NUM_QUICK_TILES = "sysui_qqs_count";
     private static final String TAG = "QuickQSPanel";
+    // Start it at 6 so a non-zero value can be obtained statically.
+    private static int sDefaultMaxTiles = 6;
 
     private boolean mDisabledByPolicy;
-    private static int mDefaultMaxTiles;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
 
@@ -69,7 +69,7 @@
             }
             removeView((View) mTileLayout);
         }
-        mDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
+        sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
         mTileLayout = new HeaderTileLayout(context);
         mTileLayout.setListening(mListening);
         addView((View) mTileLayout, 0 /* Between brightness and footer */);
@@ -107,11 +107,6 @@
     }
 
     @Override
-    protected boolean shouldShowDetail() {
-        return !mExpanded;
-    }
-
-    @Override
     protected void drawTile(TileRecord r, State state) {
         if (state instanceof SignalState) {
             SignalState copy = new SignalState();
@@ -160,14 +155,31 @@
     private final Tunable mNumTiles = new Tunable() {
         @Override
         public void onTuningChanged(String key, String newValue) {
-            setMaxTiles(getNumQuickTiles(mContext));
+            setMaxTiles(parseNumTiles(newValue));
         }
     };
 
-    public static int getNumQuickTiles(Context context) {
-        // TODO(b/140052679)
-        return whitelistIpcs(() ->
-                Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, mDefaultMaxTiles));
+    public int getNumQuickTiles() {
+        return mMaxTiles;
+    }
+
+    /**
+     * Parses the String setting into the number of tiles. Defaults to {@code mDefaultMaxTiles}
+     *
+     * @param numTilesValue value of the setting to parse
+     * @return parsed value of numTilesValue OR {@code mDefaultMaxTiles} on error
+     */
+    public static int parseNumTiles(String numTilesValue) {
+        try {
+            return Integer.parseInt(numTilesValue);
+        } catch (NumberFormatException e) {
+            // Couldn't read an int from the new setting value. Use default.
+            return sDefaultMaxTiles;
+        }
+    }
+
+    public static int getDefaultMaxTiles() {
+        return sDefaultMaxTiles;
     }
 
     void setDisabledByPolicy(boolean disabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 83b000d..38153ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -38,8 +38,8 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSDetailClipper;
@@ -52,6 +52,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Allows full-screen customization of QS, through show() and hide().
  *
@@ -66,6 +68,8 @@
 
     private final QSDetailClipper mClipper;
     private final LightBarController mLightBarController;
+    private KeyguardMonitor mKeyguardMonitor;
+    private final ScreenLifecycle mScreenLifecycle;
     private final TileQueryHelper mTileQueryHelper;
     private final View mTransparentView;
 
@@ -82,7 +86,11 @@
     private boolean mOpening;
     private boolean mIsShowingNavBackdrop;
 
-    public QSCustomizer(Context context, AttributeSet attrs) {
+    @Inject
+    public QSCustomizer(Context context, AttributeSet attrs,
+            LightBarController lightBarController,
+            KeyguardMonitor keyguardMonitor,
+            ScreenLifecycle screenLifecycle) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
 
         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
@@ -115,7 +123,9 @@
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
-        mLightBarController = Dependency.get(LightBarController.class);
+        mLightBarController = lightBarController;
+        mKeyguardMonitor = keyguardMonitor;
+        mScreenLifecycle = screenLifecycle;
         updateNavBackDrop(getResources().getConfiguration());
     }
 
@@ -177,7 +187,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(true);
             mNotifQsContainer.setCustomizerShowing(true);
-            Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            mKeyguardMonitor.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -193,7 +203,7 @@
             queryTiles();
             mNotifQsContainer.setCustomizerAnimating(false);
             mNotifQsContainer.setCustomizerShowing(true);
-            Dependency.get(KeyguardMonitor.class).addCallback(mKeyguardCallback);
+            mKeyguardMonitor.addCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -203,16 +213,21 @@
     }
 
     public void hide() {
+        final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
         if (isShown) {
             MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
             isShown = false;
             mToolbar.dismissPopupMenus();
             setCustomizing(false);
             save();
-            mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
-            mNotifQsContainer.setCustomizerAnimating(true);
+            if (animate) {
+                mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
+            } else {
+                setVisibility(View.GONE);
+            }
+            mNotifQsContainer.setCustomizerAnimating(animate);
             mNotifQsContainer.setCustomizerShowing(false);
-            Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
+            mKeyguardMonitor.removeCallback(mKeyguardCallback);
             updateNavColors();
         }
     }
@@ -268,7 +283,7 @@
 
     public void saveInstanceState(Bundle outState) {
         if (isShown) {
-            Dependency.get(KeyguardMonitor.class).removeCallback(mKeyguardCallback);
+            mKeyguardMonitor.removeCallback(mKeyguardCallback);
         }
         outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
     }
@@ -300,7 +315,7 @@
         @Override
         public void onKeyguardShowingChanged() {
             if (!isAttachedToWindow()) return;
-            if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) {
+            if (mKeyguardMonitor.isShowing() && !mOpening) {
                 hide();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index b135f7b..effea6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -159,8 +159,11 @@
             mBindTryCount++;
             try {
                 mIsBound = mContext.bindServiceAsUser(mIntent, this,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser);
+                        Context.BIND_AUTO_CREATE
+                                | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
+                                | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+                                | Context.BIND_WAIVE_PRIORITY,
+                        mUser);
             } catch (SecurityException e) {
                 Log.e(TAG, "Failed to bind to service", e);
                 mIsBound = false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 31526bf..88c7964 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -14,10 +14,12 @@
 
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
 import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -148,10 +150,15 @@
             iv.clearColorFilter();
         }
         if (state.state != mState) {
-            int color = getColor(state.state);
+            int color = getColor(state.state, state.colorActive);
             mState = state.state;
             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
-                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                if (colorIcon()) {
+                    animateColor(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+                } else {
+                    animateGrayScale(mTint, color, iv,
+                            () -> updateIcon(iv, state, allowAnimations));
+                }
                 mTint = color;
             } else {
                 if (iv instanceof AlphaControlledSlashImageView) {
@@ -168,8 +175,12 @@
         }
     }
 
+    protected int getColor(int state, int colorActive) {
+        return getColorForState(getContext(), state, colorActive);
+    }
+
     protected int getColor(int state) {
-        return getColorForState(getContext(), state);
+        return getColor(state, -1);
     }
 
     private void animateGrayScale(int fromColor, int toColor, ImageView iv,
@@ -206,6 +217,37 @@
         }
     }
 
+    private void animateColor(int fromColor, int toColor, ImageView iv,
+            final Runnable endRunnable) {
+        if (iv instanceof AlphaControlledSlashImageView) {
+            ((AlphaControlledSlashImageView) iv)
+                    .setFinalImageTintList(ColorStateList.valueOf(toColor));
+        }
+        if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) {
+
+
+            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+            anim.setDuration(QS_ANIM_LENGTH);
+            anim.addUpdateListener(animation -> {
+                float fraction = animation.getAnimatedFraction();
+                int color = (int) ArgbEvaluator.getInstance().evaluate(fraction, fromColor,
+                        toColor);
+
+                setTint(iv, color);
+            });
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endRunnable.run();
+                }
+            });
+            anim.start();
+        } else {
+            setTint(iv, toColor);
+            endRunnable.run();
+        }
+    }
+
     public static void setTint(ImageView iv, int color) {
         iv.setImageTintList(ColorStateList.valueOf(color));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index c186e59..8b7f280 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -13,6 +13,8 @@
  */
 package com.android.systemui.qs.tileimpl;
 
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
+import static com.android.systemui.qs.QSColorControllerKt.overrideColor;
 import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH;
 
 import android.animation.ValueAnimator;
@@ -34,6 +36,7 @@
 import android.util.Log;
 import android.util.PathParser;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
@@ -41,6 +44,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.Switch;
+import android.widget.TextView;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -65,12 +69,16 @@
     private boolean mShowRippleEffect = true;
 
     private final ImageView mBg;
+    private final TextView mDetailText;
     private final int mColorActive;
     private final int mColorInactive;
     private final int mColorDisabled;
     private int mCircleColor;
     private int mBgSize;
 
+    private final boolean mQsColors = overrideColor();
+    private final boolean mQSIcons = colorIcon();
+
     public QSTileBaseView(Context context, QSIconView icon) {
         this(context, icon, false);
     }
@@ -101,6 +109,12 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                 Gravity.CENTER);
         mIconFrame.addView(mIcon, params);
+
+        // "..." afforadance below icon
+        mDetailText = (TextView) LayoutInflater.from(context).inflate(R.layout.qs_tile_detail_text,
+                mIconFrame, false);
+        mIconFrame.addView(mDetailText);
+
         mIconFrame.setClipChildren(false);
         mIconFrame.setClipToPadding(false);
 
@@ -156,6 +170,10 @@
             tile.longClick();
             return true;
         });
+
+        if (tile.supportsDetailView()) {
+            mDetailText.setVisibility(View.VISIBLE);
+        }
     }
 
     public void init(OnClickListener click, OnClickListener secondaryClick,
@@ -194,7 +212,7 @@
     }
 
     protected void handleStateChanged(QSTile.State state) {
-        int circleColor = getCircleColor(state.state);
+        int circleColor = getCircleColor(state.state, mQsColors ? state.colorActive : -1);
         boolean allowAnimations = animationsEnabled();
         if (circleColor != mCircleColor) {
             if (allowAnimations) {
@@ -209,6 +227,8 @@
             mCircleColor = circleColor;
         }
 
+        mDetailText.setTextColor(QSTileImpl.getColorForState(getContext(), state.state));
+
         mShowRippleEffect = state.showRippleEffect;
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         setLongClickable(state.handlesLongClick);
@@ -239,10 +259,11 @@
         return mLocInScreen[1] >= -getHeight();
     }
 
-    private int getCircleColor(int state) {
+    private int getCircleColor(int state, int colorActive) {
         switch (state) {
             case Tile.STATE_ACTIVE:
-                return mColorActive;
+                int color = (colorActive == -1) ? mColorActive : colorActive;
+                return mQsColors && mQSIcons ? Utils.applyAlpha(0.5f, color) : color;
             case Tile.STATE_INACTIVE:
             case Tile.STATE_UNAVAILABLE:
                 return mColorDisabled;
@@ -252,6 +273,10 @@
         }
     }
 
+    private int getCircleColor(int state) {
+        return getCircleColor(state, -1);
+    }
+
     @Override
     public void setClickable(boolean clickable) {
         super.setClickable(clickable);
@@ -342,4 +367,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 9045539..681de37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -26,6 +26,7 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import static com.android.systemui.qs.QSColorControllerKt.colorIcon;
 
 import android.app.ActivityManager;
 import android.content.Context;
@@ -54,6 +55,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Prefs;
+import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSIconView;
@@ -63,6 +65,8 @@
 import com.android.systemui.qs.PagedTileLayout.TilePage;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.tiles.QSSettingsControllerKt;
+import com.android.systemui.qs.tiles.QSSettingsPanel;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -96,8 +100,8 @@
 
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
     private final Object mStaleListener = new Object();
-    protected TState mState = newTileState();
-    private TState mTmpState = newTileState();
+    protected TState mState;
+    private TState mTmpState;
     private boolean mAnnounceNextStateChange;
 
     private String mTileSpec;
@@ -121,9 +125,18 @@
      */
     abstract public int getMetricsCategory();
 
+    /**
+     * Experimental option on whether to use settings panels. Only loaded on creation, so the tile
+     * needs to be removed and added for this to take effect.
+     */
+    protected final QSSettingsPanel mQSSettingsPanelOption;
+
     protected QSTileImpl(QSHost host) {
         mHost = host;
         mContext = host.getContext();
+        mState = newTileState();
+        mTmpState = newTileState();
+        mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
     }
 
     @NonNull
@@ -288,6 +301,10 @@
     }
 
     protected void handleLongClick() {
+        if (mQSSettingsPanelOption == QSSettingsPanel.USE_DETAIL) {
+            showDetail(true);
+            return;
+        }
         Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
                 getLongClickIntent(), 0);
     }
@@ -409,6 +426,10 @@
     public abstract CharSequence getTileLabel();
 
     public static int getColorForState(Context context, int state) {
+        return getColorForState(context, state, -1);
+    }
+
+    public static int getColorForState(Context context, int state, int colorActive) {
         switch (state) {
             case Tile.STATE_UNAVAILABLE:
                 return Utils.getDisabled(context,
@@ -416,13 +437,25 @@
             case Tile.STATE_INACTIVE:
                 return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
             case Tile.STATE_ACTIVE:
-                return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
+                return getActiveColor(context, colorActive);
             default:
                 Log.e("QSTile", "Invalid state " + state);
                 return 0;
         }
     }
 
+    private static int getActiveColor(Context context, int colorActive) {
+        if (colorIcon()) {
+            if (colorActive == -1) {
+                return Utils.getColorAccentDefaultColor(context);
+            } else {
+                return colorActive;
+            }
+        } else {
+            return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
+        }
+    }
+
     protected final class H extends Handler {
         private static final int ADD_CALLBACK = 1;
         private static final int CLICK = 2;
@@ -601,4 +634,27 @@
         pw.println(this.getClass().getSimpleName() + ":");
         pw.print("    "); pw.println(getState().toString());
     }
+
+    @Override
+    public void setColor(@ColorTile int color) {
+        int resId;
+        switch(color) {
+            case COLOR_TILE_RED:
+                resId = R.color.GM2_red_500;
+                break;
+            case COLOR_TILE_BLUE:
+                resId = R.color.GM2_blue_500;
+                break;
+            case COLOR_TILE_GREEN:
+                resId = R.color.GM2_green_500;
+                break;
+            case COLOR_TILE_YELLOW:
+                resId = R.color.GM2_yellow_500;
+                break;
+            default:
+                resId = -1;
+        }
+        mTmpState.colorActive = resId == -1 ? -1 : mContext.getColor(resId);
+        refreshState();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
new file mode 100644
index 0000000..c7ef0be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QSSettingsController.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.provider.DeviceConfig
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+
+enum class QSSettingsPanel {
+    DEFAULT,
+    OPEN_LONG_PRESS,
+    OPEN_CLICK,
+    USE_DETAIL
+}
+
+fun getQSSettingsPanelOption(): QSSettingsPanel =
+        when (DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.QS_USE_SETTINGS_PANELS, 0)) {
+            1 -> QSSettingsPanel.OPEN_LONG_PRESS
+            2 -> QSSettingsPanel.OPEN_CLICK
+            3 -> QSSettingsPanel.USE_DETAIL
+            else -> QSSettingsPanel.DEFAULT
+        }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 91d38bd..187a3bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -76,8 +76,11 @@
                 UserSwitcherController.UserRecord item) {
             UserDetailItemView v = UserDetailItemView.convertOrInflate(
                     mContext, convertView, parent);
-            if (v != convertView) {
+            if (!item.isCurrent || item.isGuest) {
                 v.setOnClickListener(this);
+            } else {
+                v.setOnClickListener(null);
+                v.setClickable(false);
             }
             String name = getName(mContext, item);
             if (item.picture == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 0e7362c3..b7ce101 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -56,6 +56,7 @@
 /** Quick settings tile: Wifi **/
 public class WifiTile extends QSTileImpl<SignalState> {
     private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
+    private static final Intent WIFI_PANEL = new Intent(Settings.Panel.ACTION_WIFI);
 
     protected final NetworkController mController;
     private final AccessPointController mWifiController;
@@ -112,11 +113,21 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return WIFI_SETTINGS;
+        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_LONG_PRESS) return WIFI_PANEL;
+        else return WIFI_SETTINGS;
+    }
+
+    @Override
+    public boolean supportsDetailView() {
+        return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
     }
 
     @Override
     protected void handleClick() {
+        if (mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK) {
+            mActivityStarter.postStartActivityDismissingKeyguard(WIFI_PANEL, 0);
+            return;
+        }
         // Secondary clicks are header clicks, just toggle.
         mState.copyTo(mStateBeforeClick);
         boolean wifiEnabled = mState.value;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9178fda..156e3c0 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;
@@ -93,7 +91,6 @@
     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;
@@ -142,7 +139,7 @@
                 Dependency.get(AccessibilityController.class),
                 UnlockMethodCache.getInstance(context),
                 Dependency.get(StatusBarStateController.class),
-                KeyguardUpdateMonitor.getInstance(context));
+                Dependency.get(KeyguardUpdateMonitor.class));
     }
 
     /**
@@ -180,7 +177,6 @@
         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
         setIndicationArea(indicationArea);
-        updateDisclosure();
 
         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
         mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
@@ -193,7 +189,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 +226,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 +554,6 @@
         }
         mDozing = dozing;
         updateIndication(false);
-        updateDisclosure();
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -640,20 +613,12 @@
         }
 
         @Override
-        public void onKeyguardVisibilityChanged(boolean showing) {
-            if (showing) {
-                updateDisclosure();
-            }
-        }
-
-        @Override
         public void onBiometricHelp(int msgId, String helpString,
                 BiometricSourceType biometricSourceType) {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
             if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
                 return;
             }
-            animatePadlockError();
             boolean showSwipeToUnlock =
                     msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
@@ -674,7 +639,7 @@
         @Override
         public void onBiometricError(int msgId, String errString,
                 BiometricSourceType biometricSourceType) {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
             if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
                 return;
             }
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/NotificationLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
index 0f295ba..48e2923 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -27,6 +27,18 @@
     boolean shouldExtendLifetime(@NonNull NotificationEntry entry);
 
     /**
+     * It's possible that a notification was canceled before it ever became visible. This callback
+     * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
+     * service is canceled too quickly but we still want to make sure a FGS notification shows.
+     * @param pendingEntry the canceled (but pending) entry
+     * @return true if the notification lifetime should be extended
+     */
+    default boolean shouldExtendLifetimeForPendingNotification(
+            @NonNull NotificationEntry pendingEntry) {
+        return false;
+    }
+
+    /**
      * Sets whether or not the lifetime should be managed by the extender.  In practice, if
      * shouldManage is true, this is where the extender starts managing the entry internally and is
      * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 107b24c..f782fab 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;
@@ -281,7 +282,7 @@
         if (userId == UserHandle.USER_ALL) {
             userId = mCurrentUserId;
         }
-        return KeyguardUpdateMonitor.getInstance(mContext).isUserInLockdown(userId);
+        return Dependency.get(KeyguardUpdateMonitor.class).isUserInLockdown(userId);
     }
 
     /**
@@ -316,7 +317,7 @@
         boolean exceedsPriorityThreshold;
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            exceedsPriorityThreshold = entry.isTopBucket();
+            exceedsPriorityThreshold = entry.getBucket() != BUCKET_SILENT;
         } else {
             exceedsPriorityThreshold =
                     !getEntryManager().getNotificationData().isAmbient(entry.key);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index 3db02b9..266fe8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -26,8 +26,6 @@
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -39,22 +37,24 @@
 
     public final String key;
     public final List<Notification.Action> smartActions;
-    public final CharSequence[] smartReplies;
+    public final List<CharSequence> smartReplies;
 
     @VisibleForTesting
     NotificationUiAdjustment(
-            String key, List<Notification.Action> smartActions, CharSequence[] smartReplies) {
+            String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies) {
         this.key = key;
         this.smartActions = smartActions == null
                 ? Collections.emptyList()
-                : new ArrayList<>(smartActions);
-        this.smartReplies = smartReplies == null ? new CharSequence[0] : smartReplies.clone();
+                : smartActions;
+        this.smartReplies = smartReplies == null
+                ? Collections.emptyList()
+                : smartReplies;
     }
 
     public static NotificationUiAdjustment extractFromNotificationEntry(
             NotificationEntry entry) {
         return new NotificationUiAdjustment(
-                entry.key, entry.systemGeneratedSmartActions, entry.systemGeneratedSmartReplies);
+                entry.key, entry.getSmartActions(), entry.getSmartReplies());
     }
 
     public static boolean needReinflate(
@@ -66,7 +66,7 @@
         if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
             return true;
         }
-        if (!Arrays.equals(oldAdjustment.smartReplies, newAdjustment.smartReplies)) {
+        if (!newAdjustment.smartReplies.equals(oldAdjustment.smartReplies)) {
             return true;
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 6e75c03..50d9bae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -28,7 +28,7 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -85,7 +85,7 @@
      * possible.
      */
     private final boolean mAlwaysExpandNonGroupedNotification;
-    private final BubbleData mBubbleData;
+    private final BubbleController mBubbleController;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final KeyguardBypassController mBypassController;
 
@@ -107,8 +107,8 @@
             StatusBarStateController statusBarStateController,
             NotificationEntryManager notificationEntryManager,
             Lazy<ShadeController> shadeController,
-            BubbleData bubbleData,
             KeyguardBypassController bypassController,
+            BubbleController bubbleController,
             DynamicPrivacyController privacyController) {
         mHandler = mainHandler;
         mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,7 +121,7 @@
         Resources res = context.getResources();
         mAlwaysExpandNonGroupedNotification =
                 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
-        mBubbleData = bubbleData;
+        mBubbleController = bubbleController;
         mDynamicPrivacyController = privacyController;
         privacyController.addListener(this);
     }
@@ -147,7 +147,7 @@
         for (int i = 0; i < N; i++) {
             NotificationEntry ent = activeNotifications.get(i);
             if (ent.isRowDismissed() || ent.isRowRemoved()
-                    || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) {
+                    || mBubbleController.isBubbleNotificationSuppressedFromShade(ent.key)) {
                 // we don't want to update removed notifications because they could
                 // temporarily become children if they were isolated before.
                 continue;
@@ -436,7 +436,7 @@
             }
 
             row.showAppOpsIcons(entry.mActiveAppOps);
-            row.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs);
+            row.setLastAudiblyAlertedMs(entry.getLastAudiblyAlertedMs());
         }
 
         Trace.beginSection("NotificationPresenter#onUpdateRowStates");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
index 2a5ccdb..843c37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -70,7 +70,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.registerCallback(mCallback);
         Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
         Dependency.get(NetworkController.class).addCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 276afa7..a70dc7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.R
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -56,7 +57,8 @@
     private val wakeUpCoordinator: NotificationWakeUpCoordinator,
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManagerPhone,
-    private val roundnessManager: NotificationRoundnessManager
+    private val roundnessManager: NotificationRoundnessManager,
+    private val statusBarStateController: StatusBarStateController
 ) : Gefingerpoken {
     companion object {
         private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -188,7 +190,8 @@
             MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
             MotionEvent.ACTION_UP -> {
                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
-                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000
+                val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
+                        statusBarStateController.state != StatusBarState.SHADE
                 if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 4422a81..8b9268e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -20,6 +20,7 @@
 import android.animation.ValueAnimator;
 import android.text.format.DateFormat;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -137,6 +138,11 @@
         // Record the to-be mState and mLastState
         recordHistoricalState(state, mState);
 
+        // b/139259891
+        if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) {
+            Log.e(TAG, "Invalid state transition: SHADE -> SHADE_LOCKED", new Throwable());
+        }
+
         synchronized (mListeners) {
             String tag = getClass().getSimpleName() + "#setState(" + state + ")";
             DejankUtils.startDetectingBlockingIpcs(tag);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 91d4707..13c6f27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,7 +34,6 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
-import com.android.systemui.shared.system.SurfaceControlCompat;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -145,6 +144,7 @@
 
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+                RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
                 IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
                     throws RemoteException {
             mSourceNotification.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 0032174..b6b149d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -124,7 +124,7 @@
 
     @Inject
     public NotificationEntryManager(Context context) {
-        mNotificationData = new NotificationData();
+        mNotificationData = new NotificationData(context);
     }
 
     /** Adds a {@link NotificationEntryListener}. */
@@ -281,11 +281,25 @@
         }
 
         final NotificationEntry entry = mNotificationData.get(key);
-
-        abortExistingInflation(key);
-
         boolean lifetimeExtended = false;
 
+        // Notification was canceled before it got inflated
+        if (entry == null) {
+            NotificationEntry pendingEntry = mPendingNotifications.get(key);
+            if (pendingEntry != null) {
+                for (NotificationLifetimeExtender extender : mNotificationLifetimeExtenders) {
+                    if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
+                        extendLifetime(pendingEntry, extender);
+                        lifetimeExtended = true;
+                    }
+                }
+            }
+        }
+
+        if (!lifetimeExtended) {
+            abortExistingInflation(key);
+        }
+
         if (entry != null) {
             // If a manager needs to keep the notification around for whatever reason, we
             // keep the notification
@@ -471,7 +485,7 @@
             NotificationUiAdjustment adjustment =
                     NotificationUiAdjustment.extractFromNotificationEntry(entry);
             oldAdjustments.put(entry.key, adjustment);
-            oldImportances.put(entry.key, entry.importance);
+            oldImportances.put(entry.key, entry.getImportance());
         }
 
         // Populate notification entries from the new rankings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 154d7b35..5a0b88c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -110,7 +110,7 @@
             return true;
         }
 
-        if (entry.suspended) {
+        if (entry.isSuspended()) {
             return true;
         }
 
@@ -133,11 +133,6 @@
                 }
             }
         }
-
-        if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
-            return true;
-        }
-
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index 68d9546..9362d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
@@ -57,9 +56,8 @@
     private static final boolean ENABLE_HEADS_UP = true;
     private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
 
-    private final StatusBarStateController mStatusBarStateController =
-            Dependency.get(StatusBarStateController.class);
-    private final NotificationFilter mNotificationFilter = Dependency.get(NotificationFilter.class);
+    private final StatusBarStateController mStatusBarStateController;
+    private final NotificationFilter mNotificationFilter;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
 
     private final Context mContext;
@@ -67,7 +65,6 @@
     private final IDreamManager mDreamManager;
 
     private NotificationPresenter mPresenter;
-    private ShadeController mShadeController;
     private HeadsUpManager mHeadsUpManager;
     private HeadsUpSuppressor mHeadsUpSuppressor;
 
@@ -77,12 +74,15 @@
     private boolean mDisableNotificationAlerts;
 
     @Inject
-    public NotificationInterruptionStateProvider(Context context) {
+    public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
+            StatusBarStateController stateController) {
         this(context,
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                 IDreamManager.Stub.asInterface(
                         ServiceManager.checkService(DreamService.DREAM_SERVICE)),
-                new AmbientDisplayConfiguration(context));
+                new AmbientDisplayConfiguration(context),
+                filter,
+                stateController);
     }
 
     @VisibleForTesting
@@ -90,11 +90,15 @@
             Context context,
             PowerManager powerManager,
             IDreamManager dreamManager,
-            AmbientDisplayConfiguration ambientDisplayConfiguration) {
+            AmbientDisplayConfiguration ambientDisplayConfiguration,
+            NotificationFilter notificationFilter,
+            StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+        mNotificationFilter = notificationFilter;
+        mStatusBarStateController = statusBarStateController;
     }
 
     /** Sets up late-binding dependencies for this component. */
@@ -102,29 +106,39 @@
             NotificationPresenter notificationPresenter,
             HeadsUpManager headsUpManager,
             HeadsUpSuppressor headsUpSuppressor) {
+        setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
+                new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        boolean wasUsing = mUseHeadsUp;
+                        mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
+                                && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
+                                mContext.getContentResolver(),
+                                Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+                                Settings.Global.HEADS_UP_OFF);
+                        Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
+                        if (wasUsing != mUseHeadsUp) {
+                            if (!mUseHeadsUp) {
+                                Log.d(TAG,
+                                        "dismissing any existing heads up notification on disable"
+                                                + " event");
+                                mHeadsUpManager.releaseAllImmediately();
+                            }
+                        }
+                    }
+                });
+    }
+
+    /** Sets up late-binding dependencies for this component. */
+    public void setUpWithPresenter(
+            NotificationPresenter notificationPresenter,
+            HeadsUpManager headsUpManager,
+            HeadsUpSuppressor headsUpSuppressor,
+            ContentObserver observer) {
         mPresenter = notificationPresenter;
         mHeadsUpManager = headsUpManager;
         mHeadsUpSuppressor = headsUpSuppressor;
-
-        mHeadsUpObserver = new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean wasUsing = mUseHeadsUp;
-                mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
-                        && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
-                        mContext.getContentResolver(),
-                        Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
-                        Settings.Global.HEADS_UP_OFF);
-                Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
-                if (wasUsing != mUseHeadsUp) {
-                    if (!mUseHeadsUp) {
-                        Log.d(TAG,
-                                "dismissing any existing heads up notification on disable event");
-                        mHeadsUpManager.releaseAllImmediately();
-                    }
-                }
-            }
-        };
+        mHeadsUpObserver = observer;
 
         if (ENABLE_HEADS_UP) {
             mContext.getContentResolver().registerContentObserver(
@@ -138,13 +152,6 @@
         mHeadsUpObserver.onChange(true); // set up
     }
 
-    private ShadeController getShadeController() {
-        if (mShadeController == null) {
-            mShadeController = Dependency.get(ShadeController.class);
-        }
-        return mShadeController;
-    }
-
     /**
      * Whether the notification should appear as a bubble with a fly-out on top of the screen.
      *
@@ -153,7 +160,16 @@
      */
     public boolean shouldBubbleUp(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.notification;
-        if (!entry.canBubble) {
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
+        if (!entry.canBubble()) {
             if (DEBUG) {
                 Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
             }
@@ -177,10 +193,6 @@
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
         return true;
     }
 
@@ -201,6 +213,21 @@
     private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
+        if (!mUseHeadsUp) {
+            if (DEBUG_HEADS_UP) {
+                Log.d(TAG, "No heads up: no huns");
+            }
+            return false;
+        }
+
+        if (!canAlertCommon(entry)) {
+            return false;
+        }
+
+        if (!canAlertAwakeCommon(entry)) {
+            return false;
+        }
+
         boolean inShade = mStatusBarStateController.getState() == SHADE;
         if (entry.isBubble() && inShade) {
             if (DEBUG_HEADS_UP) {
@@ -210,18 +237,14 @@
             return false;
         }
 
-        if (!canAlertCommon(entry)) {
+        if (entry.shouldSuppressPeek()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: notification shouldn't alert: " + sbn.getKey());
+                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
             }
             return false;
         }
 
-        if (!canHeadsUpCommon(entry)) {
-            return false;
-        }
-
-        if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
+        if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
             if (DEBUG_HEADS_UP) {
                 Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
             }
@@ -284,7 +307,7 @@
             return false;
         }
 
-        if (entry.importance < NotificationManager.IMPORTANCE_DEFAULT) {
+        if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
             if (DEBUG_HEADS_UP) {
                 Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
             }
@@ -294,16 +317,13 @@
     }
 
     /**
-     * Common checks between regular heads up and when pulsing.  See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldHeadsUpWhenDozing(NotificationEntry)}.  Notifications that fail any of these
-     * checks
-     * should not alert at all.
+     * Common checks between regular & AOD heads up and bubbles.
      *
      * @param entry the entry to check
      * @return true if these checks pass, false if the notification should not alert
      */
-    protected boolean canAlertCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
         if (mNotificationFilter.shouldFilterOut(entry)) {
@@ -320,46 +340,36 @@
             }
             return false;
         }
-
         return true;
     }
 
     /**
-     * Common checks between heads up alerting and bubble fly out alerting. See
-     * {@link #shouldHeadsUp(NotificationEntry)} and
-     * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
-     * checks should not interrupt the user on screen.
+     * Common checks between alerts that occur while the device is awake (heads up & bubbles).
      *
      * @param entry the entry to check
-     * @return true if these checks pass, false if the notification should not interrupt on screen
+     * @return true if these checks pass, false if the notification should not alert
      */
-    public boolean canHeadsUpCommon(NotificationEntry entry) {
+    @VisibleForTesting
+    public boolean canAlertAwakeCommon(NotificationEntry entry) {
         StatusBarNotification sbn = entry.notification;
 
-        if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+        if (mPresenter.isDeviceInVrMode()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: no huns or vr mode");
-            }
-            return false;
-        }
-
-        if (entry.shouldSuppressPeek()) {
-            if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+                Log.d(TAG, "No alerting: no huns or vr mode");
             }
             return false;
         }
 
         if (isSnoozedPackage(sbn)) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+                Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
             }
             return false;
         }
 
         if (entry.hasJustLaunchedFullScreenIntent()) {
             if (DEBUG_HEADS_UP) {
-                Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+                Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
             }
             return false;
         }
@@ -377,6 +387,18 @@
         mHeadsUpObserver.onChange(true);
     }
 
+    /** Whether all alerts are disabled. */
+    @VisibleForTesting
+    public boolean areNotificationAlertsDisabled() {
+        return mDisableNotificationAlerts;
+    }
+
+    /** Whether HUNs should be used. */
+    @VisibleForTesting
+    public boolean getUseHeadsUp() {
+        return mUseHeadsUp;
+    }
+
     protected NotificationPresenter getPresenter() {
         return mPresenter;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 1af47dd..dfbbf98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -36,6 +36,7 @@
     private static final int[] sLocationOffset = new int[2];
 
     @Nullable private static Boolean sUseNewInterruptionModel = null;
+    @Nullable private static Boolean sUsePeopleFiltering = null;
 
     public static boolean isGrayscale(ImageView v, ContrastColorUtil colorUtil) {
         Object isGrayscale = v.getTag(R.id.icon_is_grayscale);
@@ -87,4 +88,17 @@
         }
         return sUseNewInterruptionModel;
     }
+
+    /**
+     * Caches and returns the value of the people filtering setting. Cannot change except through
+     * process restarts.
+     */
+    public static boolean usePeopleFiltering(Context context) {
+        if (sUsePeopleFiltering == null) {
+            sUsePeopleFiltering = context.getResources().getBoolean(
+                    R.bool.config_usePeopleFiltering);
+        }
+
+        return sUsePeopleFiltering;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 4a6c7d2..6fe4abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -73,7 +73,7 @@
             @Override
             public void onPreEntryUpdated(NotificationEntry entry) {
                 final boolean mAmbientStateHasChanged =
-                        entry.ambient != entry.getRow().isLowPriority();
+                        entry.isAmbient() != entry.getRow().isLowPriority();
                 if (mAmbientStateHasChanged) {
                     mLowPriorityReorderingViews.add(entry);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index 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 6178488..c3211e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -21,15 +21,20 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.Notification.EXTRA_MESSAGES;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+
 import android.annotation.NonNull;
 import android.app.Notification;
+import android.app.Notification.MessagingStyle.Message;
 import android.app.NotificationChannel;
 import android.app.NotificationManager.Policy;
 import android.app.Person;
@@ -41,7 +46,6 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 import android.widget.ImageView;
@@ -52,16 +56,15 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -86,15 +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 NotificationChannel channel;
-    public long lastAudiblyAlertedMs;
     public boolean noisy;
-    public boolean ambient;
-    public int importance;
     public StatusBarIconView icon;
     public StatusBarIconView expandedIcon;
     public StatusBarIconView centeredIcon;
@@ -104,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
@@ -120,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
 
@@ -152,23 +141,10 @@
     private boolean hasSentReply;
 
     /**
-     * Whether this notification has been approved globally, at the app level, and at the channel
-     * level for bubbling.
+     * Whether this notification has changed in visual appearance since the previous post.
+     * New notifications are interruptive by default.
      */
-    public boolean canBubble;
-
-    /**
-     * Whether this notification should be shown in the shade when it is also displayed as a bubble.
-     *
-     * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
-     * expanded</p>
-     */
-    private boolean mShowInShadeWhenBubble;
-
-    /**
-     * Whether the user has dismissed this notification when it was in bubble form.
-     */
-    private boolean mUserDismissedBubble;
+    public boolean isVisuallyInterruptive;
 
     /**
      * Whether this notification is shown to the user as a high priority notification: visible on
@@ -176,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 */
@@ -234,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();
     }
 
     /**
@@ -255,24 +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;
-
-        channel = ranking.getChannel();
-        lastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
-        importance = ranking.getImportance();
-        ambient = ranking.isAmbient();
-        snoozeCriteria = ranking.getSnoozeCriteria();
-        userSentiment = ranking.getUserSentiment();
-        systemGeneratedSmartActions = ranking.getSmartActions() == null
-                ? Collections.emptyList() : ranking.getSmartActions();
-        systemGeneratedSmartReplies = ranking.getSmartReplies() == null
-                ? new CharSequence[0]
-                : ranking.getSmartReplies().toArray(new CharSequence[0]);
-        suppressedVisualEffects = ranking.getSuppressedVisualEffects();
-        suspended = ranking.isSuspended();
-        canBubble = ranking.canBubble();
+        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;
     }
@@ -289,45 +277,39 @@
         this.mHighPriority = highPriority;
     }
 
-    /**
-     * @return True if the notif should appear in the "top" or "important" section of notifications
-     * (as opposed to the "bottom" or "silent" section). This is usually the same as
-     * {@link #isHighPriority()}, but there are certain exceptions, such as media notifs.
-     */
-    public boolean isTopBucket() {
-        return mIsTopBucket;
-    }
-    public void setIsTopBucket(boolean isTopBucket) {
-        mIsTopBucket = isTopBucket;
-    }
-
     public boolean isBubble() {
         return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
     }
 
-    public void setBubbleDismissed(boolean userDismissed) {
-        mUserDismissedBubble = userDismissed;
+    private void updatePeopleList() {
+        mAssociatedPeople.clear();
+
+        Bundle extras = notification.getNotification().extras;
+        if (extras == null) {
+            return;
+        }
+
+        List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
+
+        if (p != null) {
+            mAssociatedPeople.addAll(p);
+        }
+
+        if (Notification.MessagingStyle.class.equals(
+                notification.getNotification().getNotificationStyle())) {
+            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            if (!ArrayUtils.isEmpty(messages)) {
+                for (Notification.MessagingStyle.Message message :
+                        Notification.MessagingStyle.Message
+                                .getMessagesFromBundleArray(messages)) {
+                    mAssociatedPeople.add(message.getSenderPerson());
+                }
+            }
+        }
     }
 
-    public boolean isBubbleDismissed() {
-        return mUserDismissedBubble;
-    }
-
-    /**
-     * Sets whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public void setShowInShadeWhenBubble(boolean showInShade) {
-        mShowInShadeWhenBubble = showInShade;
-    }
-
-    /**
-     * Whether this notification should be shown in the shade when it is also displayed as a
-     * bubble.
-     */
-    public boolean showInShadeWhenBubble() {
-        // We always show it in the shade if non-clearable
-        return !isRowDismissed() && (!isClearable() || mShowInShadeWhenBubble);
+    boolean hasAssociatedPeople() {
+        return mAssociatedPeople.size() > 0;
     }
 
     /**
@@ -346,6 +328,15 @@
         }
     }
 
+    @NotificationSectionsManager.PriorityBucket
+    public int getBucket() {
+        return mBucket;
+    }
+
+    public void setBucket(@NotificationSectionsManager.PriorityBucket int bucket) {
+        mBucket = bucket;
+    }
+
     public ExpandableNotificationRow getRow() {
         return row;
     }
@@ -524,72 +515,6 @@
     }
 
     /**
-     * Returns our best guess for the most relevant text summary of the latest update to this
-     * notification, based on its type. Returns null if there should not be an update message.
-     */
-    public CharSequence getUpdateMessage(Context context) {
-        final Notification underlyingNotif = notification.getNotification();
-        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
-        try {
-            if (Notification.BigTextStyle.class.equals(style)) {
-                // Return the big text, it is big so probably important. If it's not there use the
-                // normal text.
-                CharSequence bigText =
-                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
-                return !TextUtils.isEmpty(bigText)
-                        ? bigText
-                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            } else if (Notification.MessagingStyle.class.equals(style)) {
-                final List<Notification.MessagingStyle.Message> messages =
-                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
-                                (Parcelable[]) underlyingNotif.extras.get(
-                                        Notification.EXTRA_MESSAGES));
-
-                final Notification.MessagingStyle.Message latestMessage =
-                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
-
-                if (latestMessage != null) {
-                    final CharSequence personName = latestMessage.getSenderPerson() != null
-                            ? latestMessage.getSenderPerson().getName()
-                            : null;
-
-                    // Prepend the sender name if available since group chats also use messaging
-                    // style.
-                    if (!TextUtils.isEmpty(personName)) {
-                        return context.getResources().getString(
-                                R.string.notification_summary_message_format,
-                                personName,
-                                latestMessage.getText());
-                    } else {
-                        return latestMessage.getText();
-                    }
-                }
-            } else if (Notification.InboxStyle.class.equals(style)) {
-                CharSequence[] lines =
-                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
-                // Return the last line since it should be the most recent.
-                if (lines != null && lines.length > 0) {
-                    return lines[lines.length - 1];
-                }
-            } else if (Notification.MediaStyle.class.equals(style)) {
-                // Return nothing, media updates aren't typically useful as a text update.
-                return null;
-            } else {
-                // Default to text extra.
-                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
-            }
-        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
-            // No use crashing, we'll just return null and the caller will assume there's no update
-            // message.
-            e.printStackTrace();
-        }
-
-        return null;
-    }
-
-    /**
      * Abort all existing inflation tasks
      */
     public void abortTask() {
@@ -650,21 +575,18 @@
         if (!ArrayUtils.isEmpty(replyTexts)) {
             return true;
         }
-        Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
-        if (messages != null && messages.length > 0) {
-            Parcelable message = messages[messages.length - 1];
-            if (message instanceof Bundle) {
-                Notification.MessagingStyle.Message lastMessage =
-                        Notification.MessagingStyle.Message.getMessageFromBundle(
-                                (Bundle) message);
-                if (lastMessage != null) {
-                    Person senderPerson = lastMessage.getSenderPerson();
-                    if (senderPerson == null) {
-                        return true;
-                    }
-                    Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
-                    return Objects.equals(user, senderPerson);
+        List<Message> messages = Message.getMessagesFromBundleArray(
+                extras.getParcelableArray(Notification.EXTRA_MESSAGES));
+        if (messages != null && !messages.isEmpty()) {
+            Message lastMessage = messages.get(messages.size() -1);
+
+            if (lastMessage != null) {
+                Person senderPerson = lastMessage.getSenderPerson();
+                if (senderPerson == null) {
+                    return true;
                 }
+                Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);
+                return Objects.equals(user, senderPerson);
             }
         }
         return false;
@@ -893,7 +815,7 @@
         if (isExemptFromDndVisualSuppression()) {
             return false;
         }
-        return (suppressedVisualEffects & effect) != 0;
+        return (getSuppressedVisualEffects() & effect) != 0;
     }
 
     /**
@@ -936,6 +858,16 @@
         return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_NOTIFICATION_LIST);
     }
 
+
+    /**
+     * Returns whether {@link Policy#SUPPRESSED_EFFECT_BADGE}
+     * is set for this entry. This badge is not an app badge, but rather an indicator of "unseen"
+     * content. Typically this is referred to as a "dot" internally in Launcher & SysUI code.
+     */
+    public boolean shouldSuppressNotificationDot() {
+        return shouldSuppressVisualEffect(SUPPRESSED_EFFECT_BADGE);
+    }
+
     /**
      * Categories that are explicitly called out on DND settings screens are always blocked, if
      * DND has flagged them, even if they are foreground or system notifications that might
@@ -1003,12 +935,4 @@
             this.index = index;
         }
     }
-
-    /**
-     * Returns whether the notification is a foreground service. It shows that this is an ongoing
-     * bubble.
-     */
-    public boolean isForegroundService() {
-        return (notification.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 247c31f..60cf995 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -214,7 +214,7 @@
                 // no-op here.
             }
         } else {
-            if (oldImportance != null && entry.importance != oldImportance) {
+            if (oldImportance != null && entry.getImportance() != oldImportance) {
                 if (entry.rowExists()) {
                     entry.getRow().onNotificationRankingUpdated();
                 }
@@ -228,7 +228,7 @@
             PackageManager pmUser,
             StatusBarNotification sbn,
             ExpandableNotificationRow row) {
-        row.setIsLowPriority(entry.ambient);
+        row.setIsLowPriority(entry.isAmbient());
 
         // Extract target SDK version.
         try {
@@ -248,7 +248,7 @@
         row.setOnActivatedListener(mPresenter);
 
         boolean useIncreasedCollapsedHeight =
-                mMessagingUtil.isImportantMessaging(sbn, entry.importance);
+                mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
         boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
                 && !mPresenter.isPresenterFullyCollapsed();
         row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 65e744b..0f6ce21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -520,7 +520,7 @@
     public boolean getIsNonblockable() {
         boolean isNonblockable = Dependency.get(NotificationBlockingHelperManager.class)
                 .isNonblockable(mStatusBarNotification.getPackageName(),
-                        mEntry.channel.getId());
+                        mEntry.getChannel().getId());
 
         // If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
         // again, but in-place on the main thread this time. This should rarely ever get called.
@@ -532,13 +532,13 @@
             mEntry.mIsSystemNotification = isSystemNotification(mContext, mStatusBarNotification);
         }
 
-        isNonblockable |= mEntry.channel.isImportanceLockedByOEM();
-        isNonblockable |= mEntry.channel.isImportanceLockedByCriticalDeviceFunction();
+        isNonblockable |= mEntry.getChannel().isImportanceLockedByOEM();
+        isNonblockable |= mEntry.getChannel().isImportanceLockedByCriticalDeviceFunction();
 
         if (!isNonblockable && mEntry != null && mEntry.mIsSystemNotification != null) {
             if (mEntry.mIsSystemNotification) {
-                if (mEntry.channel != null
-                        && !mEntry.channel.isBlockableSystem()) {
+                if (mEntry.getChannel() != null
+                        && !mEntry.getChannel().isBlockableSystem()) {
                     isNonblockable = true;
                 }
             }
@@ -2311,9 +2311,6 @@
 
     @Override
     public int getIntrinsicHeight() {
-        if (isShownAsBubble()) {
-            return getMaxExpandHeight();
-        }
         if (isUserLocked()) {
             return getActualHeight();
         }
@@ -2359,10 +2356,6 @@
         return mStatusbarStateController != null && mStatusbarStateController.isDozing();
     }
 
-    private boolean isShownAsBubble() {
-        return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
-    }
-
     @Override
     public boolean isGroupExpanded() {
         return mGroupManager.isGroupExpanded(mStatusBarNotification);
@@ -2396,7 +2389,7 @@
     public ArraySet<NotificationChannel> getUniqueChannels() {
         ArraySet<NotificationChannel> channels = new ArraySet<>();
 
-        channels.add(mEntry.channel);
+        channels.add(mEntry.getChannel());
 
         // If this is a summary, then add in the children notification channels for the
         // same user and pkg.
@@ -2405,7 +2398,7 @@
             final int numChildren = childrenRows.size();
             for (int i = 0; i < numChildren; i++) {
                 final ExpandableNotificationRow childRow = childrenRows.get(i);
-                final NotificationChannel childChannel = childRow.getEntry().channel;
+                final NotificationChannel childChannel = childRow.getEntry().getChannel();
                 final StatusBarNotification childSbn = childRow.getStatusBarNotification();
                 if (childSbn.getUser().equals(mStatusBarNotification.getUser()) &&
                         childSbn.getPackageName().equals(mStatusBarNotification.getPackageName())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 2c15e87..73093c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -87,7 +87,7 @@
         // - The row is blockable (i.e. not non-blockable)
         // - The dismissed row is a valid group (>1 or 0 children from the same channel)
         // or the only child in the group
-        if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG)
+        if ((row.getEntry().getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG)
                 && mIsShadeExpanded
                 && !row.getIsNonblockable()
                 && ((!row.isChildInGroup() || row.isOnlyChildInGroup())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 48a8295..a612a17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -264,7 +264,7 @@
                 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
                 packageContext);
         result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
-                mRow.getContext(), mRow.getHeadsUpManager(),
+                mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                 mRow.getExistingSmartRepliesAndActions());
         apply(
                 inflateSynchronously,
@@ -311,20 +311,21 @@
 
     private static InflationProgress inflateSmartReplyViews(InflationProgress result,
             @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
-            HeadsUpManager headsUpManager, SmartRepliesAndActions previousSmartRepliesAndActions) {
+            Context packageContext, HeadsUpManager headsUpManager,
+            SmartRepliesAndActions previousSmartRepliesAndActions) {
         SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
         SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
         if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
             result.expandedInflatedSmartReplies =
                     InflatedSmartReplies.inflate(
-                            context, entry, smartReplyConstants, smartReplyController,
-                            headsUpManager, previousSmartRepliesAndActions);
+                            context, packageContext, entry, smartReplyConstants,
+                            smartReplyController, headsUpManager, previousSmartRepliesAndActions);
         }
         if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
             result.headsUpInflatedSmartReplies =
                     InflatedSmartReplies.inflate(
-                            context, entry, smartReplyConstants, smartReplyController,
-                            headsUpManager, previousSmartRepliesAndActions);
+                            context, packageContext, entry, smartReplyConstants,
+                            smartReplyController, headsUpManager, previousSmartRepliesAndActions);
         }
         return result;
     }
@@ -817,7 +818,7 @@
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
                 return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
-                        mRow.getContext(), mRow.getHeadsUpManager(),
+                        mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
                 mError = e;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 0c5b27b..f30a8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1139,19 +1139,21 @@
     }
 
     private void applyMediaTransfer(final NotificationEntry entry) {
+        if (!entry.isMediaNotification()) {
+            return;
+        }
+
         View bigContentView = mExpandedChild;
-        if (bigContentView == null || !entry.isMediaNotification()) {
-            return;
+        if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
+            mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView,
+                    entry);
         }
 
-        View mediaActionContainer = bigContentView.findViewById(
-                com.android.internal.R.id.media_actions);
-        if (!(mediaActionContainer instanceof LinearLayout)) {
-            return;
+        View smallContentView = mContractedChild;
+        if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
+            mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView,
+                    entry);
         }
-
-        mMediaTransferManager.applyMediaTransferView((ViewGroup) mediaActionContainer,
-                entry);
     }
 
     private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
@@ -1181,7 +1183,7 @@
                     mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
                             mCurrentSmartRepliesAndActions.smartActions.actions.size(),
                     mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
-                            mCurrentSmartRepliesAndActions.smartReplies.choices.length));
+                            mCurrentSmartRepliesAndActions.smartReplies.choices.size()));
         }
         applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
     }
@@ -1295,7 +1297,7 @@
                 if (smartRepliesAndActions.smartReplies != null
                         || smartRepliesAndActions.smartActions != null) {
                     int numSmartReplies = smartRepliesAndActions.smartReplies == null
-                            ? 0 : smartRepliesAndActions.smartReplies.choices.length;
+                            ? 0 : smartRepliesAndActions.smartReplies.choices.size();
                     int numSmartActions = smartRepliesAndActions.smartActions == null
                             ? 0 : smartRepliesAndActions.smartActions.actions.size();
                     boolean fromAssistant = smartRepliesAndActions.smartReplies == null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 8f7671a..9d0dd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -241,7 +241,7 @@
 
         notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper());
         notificationSnoozeView.setStatusBarNotification(sbn);
-        notificationSnoozeView.setSnoozeOptions(row.getEntry().snoozeCriteria);
+        notificationSnoozeView.setSnoozeOptions(row.getEntry().getSnoozeCriteria());
         guts.setHeightChangedListener((NotificationGuts g) -> {
             mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */);
         });
@@ -316,7 +316,7 @@
                 iNotificationManager,
                 mVisualStabilityManager,
                 packageName,
-                row.getEntry().channel,
+                row.getEntry().getChannel(),
                 row.getUniqueChannels(),
                 sbn,
                 mCheckSaveListener,
@@ -325,7 +325,7 @@
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 isForBlockingHelper,
-                row.getEntry().importance,
+                row.getEntry().getImportance(),
                 row.getEntry().isHighPriority());
 
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 4221846..ec0c634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -16,11 +16,10 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.NUM_SECTIONS;
-
-
+import android.content.Context;
 import android.util.MathUtils;
 
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,11 +50,14 @@
     private float mAppearFraction;
 
     @Inject
-    NotificationRoundnessManager(KeyguardBypassController keyguardBypassController) {
-        mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
-        mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
+    NotificationRoundnessManager(
+            KeyguardBypassController keyguardBypassController,
+            Context context) {
+        int numberOfSections = NotificationData.getNotificationBuckets(context).length;
+        mFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mLastInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mTmpFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
+        mTmpLastInSectionViews = new ActivatableNotificationView[numberOfSections];
         mBypassController = keyguardBypassController;
     }
 
@@ -157,7 +159,7 @@
 
     public void updateRoundedChildren(NotificationSection[] sections) {
         boolean anyChanged = false;
-        for (int i = 0; i < NUM_SECTIONS; i++) {
+        for (int i = 0; i < sections.length; i++) {
             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
             mTmpLastInSectionViews[i] = mLastInSectionViews[i];
             mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index f39ed2e..9d456ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -33,6 +33,7 @@
  * bounds change.
  */
 class NotificationSection {
+    private @NotificationSectionsManager.PriorityBucket int mBucket;
     private View mOwningView;
     private Rect mBounds = new Rect();
     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
@@ -43,8 +44,9 @@
     private ActivatableNotificationView mFirstVisibleChild;
     private ActivatableNotificationView mLastVisibleChild;
 
-    NotificationSection(View owningView) {
+    NotificationSection(View owningView, @NotificationSectionsManager.PriorityBucket int bucket) {
         mOwningView = owningView;
+        mBucket = bucket;
     }
 
     public void cancelAnimators() {
@@ -72,6 +74,11 @@
         return mBottomAnimator != null || mTopAnimator != null;
     }
 
+    @NotificationSectionsManager.PriorityBucket
+    public int getBucket() {
+        return mBucket;
+    }
+
     public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) {
         // Left and right bounds are always applied immediately.
         mCurrentBounds.left = mBounds.left;
@@ -199,12 +206,16 @@
         return mLastVisibleChild;
     }
 
-    public void setFirstVisibleChild(ActivatableNotificationView child) {
+    public boolean setFirstVisibleChild(ActivatableNotificationView child) {
+        boolean changed = mFirstVisibleChild != child;
         mFirstVisibleChild = child;
+        return changed;
     }
 
-    public void setLastVisibleChild(ActivatableNotificationView child) {
+    public boolean setLastVisibleChild(ActivatableNotificationView child) {
+        boolean changed = mLastVisibleChild != child;
         mLastVisibleChild = child;
+        return changed;
     }
 
     public void resetCurrentBounds() {
@@ -291,5 +302,4 @@
         mBounds.bottom = bottom;
         return bottom;
     }
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index d119fb79..d0444ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -18,6 +18,10 @@
 
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.provider.Settings;
@@ -34,23 +38,31 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
 
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Manages the boundaries of the two notification sections (high priority and low priority). Also
  * shows/hides the headers for those sections where appropriate.
  *
  * TODO: Move remaining sections logic from NSSL into this class.
  */
-class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+public class NotificationSectionsManager implements StackScrollAlgorithm.SectionProvider {
+
+    private static final String TAG = "NotifSectionsManager";
+    private static final boolean DEBUG = false;
+
     private final NotificationStackScrollLayout mParent;
     private final ActivityStarter mActivityStarter;
     private final StatusBarStateController mStatusBarStateController;
     private final ConfigurationController mConfigurationController;
-    private final boolean mUseMultipleSections;
+    private final int mNumberOfSections;
 
     private boolean mInitialized = false;
     private SectionHeaderView mGentleHeader;
     private boolean mGentleHeaderVisible = false;
-    @Nullable private ExpandableNotificationRow mFirstGentleNotif;
+
     @Nullable private View.OnClickListener mOnClearGentleNotifsClickListener;
 
     NotificationSectionsManager(
@@ -58,12 +70,21 @@
             ActivityStarter activityStarter,
             StatusBarStateController statusBarStateController,
             ConfigurationController configurationController,
-            boolean useMultipleSections) {
+            int numberOfSections) {
         mParent = parent;
         mActivityStarter = activityStarter;
         mStatusBarStateController = statusBarStateController;
         mConfigurationController = configurationController;
-        mUseMultipleSections = useMultipleSections;
+        mNumberOfSections = numberOfSections;
+    }
+
+    NotificationSection[] createSectionsForBuckets(int[] buckets) {
+        NotificationSection[] sections = new NotificationSection[buckets.length];
+        for (int i = 0; i < buckets.length; i++) {
+            sections[i] = new NotificationSection(mParent, buckets[i] /* bucket */);
+        }
+
+        return sections;
     }
 
     /** Must be called before use. */
@@ -111,8 +132,38 @@
     }
 
     @Override
-    public boolean beginsSection(View view) {
-        return view == getFirstLowPriorityChild();
+    public boolean beginsSection(@NonNull View view, @Nullable View previous) {
+        boolean begin = false;
+        if (view instanceof ExpandableNotificationRow) {
+            if (previous instanceof ExpandableNotificationRow) {
+                // If we're drawing the first non-person notification, break out a section
+                ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
+                ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+
+                begin =  curr.getEntry().getBucket() != prev.getEntry().getBucket();
+            }
+        }
+
+        if (!begin) {
+            begin = view == mGentleHeader;
+        }
+
+        return begin;
+    }
+
+    private boolean isUsingMultipleSections() {
+        return mNumberOfSections > 1;
+    }
+
+    private @PriorityBucket int getBucket(ActivatableNotificationView view)
+            throws IllegalArgumentException {
+        if (view instanceof ExpandableNotificationRow) {
+            return ((ExpandableNotificationRow) view).getEntry().getBucket();
+        } else if (view == mGentleHeader) {
+            return BUCKET_SILENT;
+        }
+
+        throw new IllegalArgumentException("I don't know how to find a bucket for this view :(");
     }
 
     /**
@@ -120,11 +171,10 @@
      * bookkeeping and adds/moves/removes section headers if appropriate.
      */
     void updateSectionBoundaries() {
-        if (!mUseMultipleSections) {
+        if (!isUsingMultipleSections()) {
             return;
         }
 
-        mFirstGentleNotif = null;
         int firstGentleNotifIndex = -1;
 
         final int n = mParent.getChildCount();
@@ -133,9 +183,8 @@
             if (child instanceof ExpandableNotificationRow
                     && child.getVisibility() != View.GONE) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!row.getEntry().isTopBucket()) {
+                if (row.getEntry().getBucket() == BUCKET_SILENT) {
                     firstGentleNotifIndex = i;
-                    mFirstGentleNotif = row;
                     break;
                 }
             }
@@ -184,80 +233,75 @@
     }
 
     /**
-     * Updates the boundaries (as tracked by their first and last views) of the high and low
-     * priority sections.
+     * Updates the boundaries (as tracked by their first and last views) of the priority sections.
      *
      * @return {@code true} If the last view in the top section changed (so we need to animate).
      */
-    boolean updateFirstAndLastViewsInSections(
-            final NotificationSection highPrioritySection,
-            final NotificationSection lowPrioritySection,
-            ActivatableNotificationView firstChild,
-            ActivatableNotificationView lastChild) {
-        if (mUseMultipleSections) {
-            ActivatableNotificationView previousLastHighPriorityChild =
-                    highPrioritySection.getLastVisibleChild();
-            ActivatableNotificationView previousFirstLowPriorityChild =
-                    lowPrioritySection.getFirstVisibleChild();
-            ActivatableNotificationView lastHighPriorityChild = getLastHighPriorityChild();
-            ActivatableNotificationView firstLowPriorityChild = getFirstLowPriorityChild();
-            if (lastHighPriorityChild != null && firstLowPriorityChild != null) {
-                highPrioritySection.setFirstVisibleChild(firstChild);
-                highPrioritySection.setLastVisibleChild(lastHighPriorityChild);
-                lowPrioritySection.setFirstVisibleChild(firstLowPriorityChild);
-                lowPrioritySection.setLastVisibleChild(lastChild);
-            } else if (lastHighPriorityChild != null) {
-                highPrioritySection.setFirstVisibleChild(firstChild);
-                highPrioritySection.setLastVisibleChild(lastChild);
-                lowPrioritySection.setFirstVisibleChild(null);
-                lowPrioritySection.setLastVisibleChild(null);
-            } else {
-                highPrioritySection.setFirstVisibleChild(null);
-                highPrioritySection.setLastVisibleChild(null);
-                lowPrioritySection.setFirstVisibleChild(firstChild);
-                lowPrioritySection.setLastVisibleChild(lastChild);
+    boolean updateFirstAndLastViewsForAllSections(
+            NotificationSection[] sections,
+            List<ActivatableNotificationView> children) {
+
+        if (sections.length <= 0 || children.size() <= 0) {
+            for (NotificationSection s : sections) {
+                s.setFirstVisibleChild(null);
+                s.setLastVisibleChild(null);
             }
-            return lastHighPriorityChild != previousLastHighPriorityChild
-                    || firstLowPriorityChild != previousFirstLowPriorityChild;
-        } else {
-            highPrioritySection.setFirstVisibleChild(firstChild);
-            highPrioritySection.setLastVisibleChild(lastChild);
             return false;
         }
+
+        boolean changed = false;
+        ArrayList<ActivatableNotificationView> viewsInBucket = new ArrayList<>();
+        for (NotificationSection s : sections) {
+            int filter = s.getBucket();
+            viewsInBucket.clear();
+
+            //TODO: do this in a single pass, and more better
+            for (ActivatableNotificationView v : children)  {
+                if (getBucket(v) == filter) {
+                    viewsInBucket.add(v);
+                }
+
+                if (viewsInBucket.size() >= 1) {
+                    changed |= s.setFirstVisibleChild(viewsInBucket.get(0));
+                    changed |= s.setLastVisibleChild(viewsInBucket.get(viewsInBucket.size() - 1));
+                } else {
+                    changed |= s.setFirstVisibleChild(null);
+                    changed |= s.setLastVisibleChild(null);
+                }
+            }
+        }
+
+        if (DEBUG) {
+            logSections(sections);
+        }
+
+        return changed;
     }
 
+    private void logSections(NotificationSection[] sections) {
+        for (int i = 0; i < sections.length; i++) {
+            NotificationSection s = sections[i];
+            ActivatableNotificationView first = s.getFirstVisibleChild();
+            String fs = first == null ? "(null)"
+                    :  (first instanceof ExpandableNotificationRow)
+                            ? ((ExpandableNotificationRow) first).getEntry().key
+                            : Integer.toHexString(System.identityHashCode(first));
+            ActivatableNotificationView last = s.getLastVisibleChild();
+            String ls = last == null ? "(null)"
+                    :  (last instanceof ExpandableNotificationRow)
+                            ? ((ExpandableNotificationRow) last).getEntry().key
+                            : Integer.toHexString(System.identityHashCode(last));
+            android.util.Log.d(TAG, "updateSections: f=" + fs + " s=" + i);
+            android.util.Log.d(TAG, "updateSections: l=" + ls + " s=" + i);
+        }
+    }
+
+
     @VisibleForTesting
     SectionHeaderView getGentleHeaderView() {
         return mGentleHeader;
     }
 
-    @Nullable
-    private ActivatableNotificationView getFirstLowPriorityChild() {
-        if (mGentleHeaderVisible) {
-            return mGentleHeader;
-        } else {
-            return mFirstGentleNotif;
-        }
-    }
-
-    @Nullable
-    private ActivatableNotificationView getLastHighPriorityChild() {
-        ActivatableNotificationView lastChildBeforeGap = null;
-        int childCount = mParent.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = mParent.getChildAt(i);
-            if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-                if (!row.getEntry().isTopBucket()) {
-                    break;
-                } else {
-                    lastChildBeforeGap = row;
-                }
-            }
-        }
-        return lastChildBeforeGap;
-    }
-
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
         @Override
         public void onLocaleListChanged() {
@@ -279,4 +323,20 @@
             mOnClearGentleNotifsClickListener.onClick(v);
         }
     }
+
+    /**
+     * For now, declare the available notification buckets (sections) here so that other
+     * presentation code can decide what to do based on an entry's buckets
+     *
+     */
+    @Retention(SOURCE)
+    @IntDef(prefix = { "BUCKET_" }, value = {
+            BUCKET_PEOPLE,
+            BUCKET_ALERTING,
+            BUCKET_SILENT
+    })
+    public @interface PriorityBucket {}
+    public static final int BUCKET_PEOPLE = 0;
+    public static final int BUCKET_ALERTING = 1;
+    public static final int BUCKET_SILENT = 2;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 6a611a6..a67018e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
 import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
@@ -112,6 +113,7 @@
 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -172,7 +174,6 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
-    static final int NUM_SECTIONS = 2;
     /**
      * The distance in pixels between sections when the sections are directly adjacent (no visible
      * gap is drawn between them). In this case we don't want to round their corners.
@@ -351,7 +352,7 @@
             return true;
         }
     };
-    private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
+    private NotificationSection[] mSections;
     private boolean mAnimateNextBackgroundTop;
     private boolean mAnimateNextBackgroundBottom;
     private boolean mAnimateNextSectionBoundsChange;
@@ -522,9 +523,6 @@
 
         mAllowLongPress = allowLongPress;
 
-        for (int i = 0; i < NUM_SECTIONS; i++) {
-            mSections[i] = new NotificationSection(this);
-        }
         mRoundnessManager = notificationRoundnessManager;
 
         mHeadsUpManager = headsUpManager;
@@ -533,19 +531,21 @@
         mKeyguardBypassController = keyguardBypassController;
         mFalsingManager = falsingManager;
 
+        int[] buckets = NotificationData.getNotificationBuckets(context);
         mSectionsManager =
                 new NotificationSectionsManager(
                         this,
                         activityStarter,
                         statusBarStateController,
                         configurationController,
-                        NotificationUtils.useNewInterruptionModel(context));
+                        buckets.length);
         mSectionsManager.initialize(LayoutInflater.from(context));
         mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
             // Leave the shade open if there will be other notifs left over to clear
             final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
             clearNotifications(ROWS_GENTLE, closeShade);
         });
+        mSections = mSectionsManager.createSectionsForBuckets(buckets);
 
         mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
@@ -773,7 +773,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground
                 && (mSections[0].getCurrentBounds().top
-                < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
+                < mSections[mSections.length - 1].getCurrentBounds().bottom
                 || mAmbientState.isDozing())) {
             drawBackground(canvas);
         } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
@@ -819,7 +819,7 @@
         int lockScreenLeft = mSidePaddings;
         int lockScreenRight = getWidth() - mSidePaddings;
         int lockScreenTop = mSections[0].getCurrentBounds().top;
-        int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
+        int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
         int hiddenLeft = getWidth() / 2;
         int hiddenTop = mTopPadding;
 
@@ -2636,6 +2636,21 @@
         return null;
     }
 
+    //TODO: We shouldn't have to generate this list every time
+    private List<ActivatableNotificationView> getChildrenWithBackground() {
+        ArrayList<ActivatableNotificationView> children = new ArrayList<>();
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
+                children.add((ActivatableNotificationView) child);
+            }
+        }
+
+        return children;
+    }
+
     /**
      * Fling the scroll view
      *
@@ -3198,8 +3213,8 @@
 
         ActivatableNotificationView firstChild = getFirstChildWithBackground();
         ActivatableNotificationView lastChild = getLastChildWithBackground();
-        boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
-                mSections[0], mSections[1], firstChild, lastChild);
+        boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
+                mSections, getChildrenWithBackground());
 
         if (mAnimationsEnabled && mIsExpanded) {
             mAnimateNextBackgroundTop = firstChild != previousFirstChild;
@@ -5780,9 +5795,9 @@
             currentIndex++;
             boolean beforeSpeedBump;
             if (mHighPriorityBeforeSpeedBump) {
-                beforeSpeedBump = row.getEntry().isTopBucket();
+                beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT;
             } else {
-                beforeSpeedBump = !row.getEntry().ambient;
+                beforeSpeedBump = !row.getEntry().isAmbient();
             }
             if (beforeSpeedBump) {
                 speedBumpIndex = currentIndex;
@@ -5838,9 +5853,9 @@
             case ROWS_ALL:
                 return true;
             case ROWS_HIGH_PRIORITY:
-                return row.getEntry().isTopBucket();
+                return row.getEntry().getBucket() < BUCKET_SILENT;
             case ROWS_GENTLE:
-                return !row.getEntry().isTopBucket();
+                return row.getEntry().getBucket() == BUCKET_SILENT;
             default:
                 throw new IllegalArgumentException("Unknown selection: " + selection);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index ef80484..4b61064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
@@ -411,8 +413,10 @@
             float currentYPosition,
             boolean reverse) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
+        ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
         final boolean applyGapHeight =
-                childNeedsGapHeight(ambientState.getSectionProvider(), algorithmState, i, child);
+                childNeedsGapHeight(
+                        ambientState.getSectionProvider(), algorithmState, i, child, previousChild);
         ExpandableViewState childViewState = child.getViewState();
         childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
 
@@ -477,8 +481,11 @@
             SectionProvider sectionProvider,
             StackScrollAlgorithmState algorithmState,
             int visibleIndex,
-            View child) {
-        boolean needsGapHeight = sectionProvider.beginsSection(child) && visibleIndex > 0;
+            View child,
+            View previousChild) {
+
+        boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
+                && visibleIndex > 0;
         if (ANCHOR_SCROLLING) {
             needsGapHeight &= visibleIndex != algorithmState.anchorViewIndex;
         }
@@ -749,6 +756,6 @@
          * True if this view starts a new "section" of notifications, such as the gentle
          * notifications section. False if sections are not enabled.
          */
-        boolean beginsSection(View view);
+        boolean beginsSection(@NonNull View view, @Nullable View previous);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 41c6a7b..2d012c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -448,9 +448,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/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/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index ab9162a..83ecb55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -120,7 +120,6 @@
     private KeyguardAffordanceView mRightAffordanceView;
     private KeyguardAffordanceView mLeftAffordanceView;
     private ViewGroup mIndicationArea;
-    private TextView mEnterpriseDisclosure;
     private TextView mIndicationText;
     private ViewGroup mPreviewContainer;
     private ViewGroup mOverlayContainer;
@@ -234,8 +233,6 @@
         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);
@@ -278,7 +275,7 @@
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         getContext().registerReceiverAsUser(mDevicePolicyReceiver,
                 UserHandle.ALL, filter, null, null);
-        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         mUnlockMethodCache.addListener(this);
     }
 
@@ -290,7 +287,7 @@
         mRightExtension.destroy();
         mLeftExtension.destroy();
         getContext().unregisterReceiver(mDevicePolicyReceiver);
-        KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
     }
 
     private void initAccessibility() {
@@ -312,9 +309,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));
@@ -817,7 +811,7 @@
 
         @Override
         public Intent getIntent() {
-            KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+            KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
             boolean canSkipBouncer = updateMonitor.getUserCanSkipBouncer(
                     KeyguardUpdateMonitor.getCurrentUser());
             boolean secure = mUnlockMethodCache.isMethodSecure();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 2e7ba045c..d7f67ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,9 +41,9 @@
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.R;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.DejankUtils;
+import com.android.systemui.R;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.plugins.FalsingManager;
 
@@ -93,7 +93,6 @@
     private int mBouncerPromptReason;
     private boolean mIsAnimatingAway;
     private boolean mIsScrimmed;
-    private ViewGroup mLockIconContainer;
 
     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
             LockPatternUtils lockPatternUtils, ViewGroup container,
@@ -190,10 +189,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 +369,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/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index f4635d1..00b764b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -16,23 +16,22 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.content.Context
 import android.hardware.Sensor
 import android.hardware.TriggerEvent
 import android.hardware.TriggerEventListener
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Dependency
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.Assert
-import com.android.systemui.util.AsyncSensorManager
+import com.android.systemui.util.sensors.AsyncSensorManager
 
 class KeyguardLiftController constructor(
-    context: Context,
     private val statusBarStateController: StatusBarStateController,
     private val asyncSensorManager: AsyncSensorManager
 ) : StatusBarStateController.StateListener, KeyguardUpdateMonitorCallback() {
 
-    private val keyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context)
+    private val keyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor::class.java)
     private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
     private var isListening = false
     private var bouncerVisible = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 06a2225..6781073 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.dock.DockManager;
@@ -104,6 +105,7 @@
     private boolean mShowingLaunchAffordance;
     private boolean mKeyguardJustShown;
     private boolean mUpdatePending;
+    private boolean mBouncerPreHideAnimation;
 
     private final KeyguardMonitor.Callback mKeyguardMonitorCallback =
             new KeyguardMonitor.Callback() {
@@ -124,9 +126,12 @@
 
                 @Override
                 public void onKeyguardFadingAwayChanged() {
-                    if (!mKeyguardMonitor.isKeyguardFadingAway() && mBlockUpdates) {
-                        mBlockUpdates = false;
-                        update(true /* force */);
+                    if (!mKeyguardMonitor.isKeyguardFadingAway()) {
+                        mBouncerPreHideAnimation = false;
+                        if (mBlockUpdates) {
+                            mBlockUpdates = false;
+                            update(true /* force */);
+                        }
                     }
                 }
             };
@@ -182,7 +187,7 @@
         super(context, attrs);
         mContext = context;
         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mAccessibilityController = accessibilityController;
         mConfigurationController = configurationController;
         mStatusBarStateController = statusBarStateController;
@@ -463,6 +468,14 @@
         }
     }
 
+    /**
+     * Animate padlock opening when bouncer challenge is solved.
+     */
+    public void onBouncerPreHideAnimation() {
+        mBouncerPreHideAnimation = true;
+        update();
+    }
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({ERROR, UNLOCK, LOCK, SCANNING})
     @interface LockAnimIndex {}
@@ -509,8 +522,8 @@
     }
 
     private int getState() {
-        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        if ((mUnlockMethodCache.canSkipBouncer() || !mKeyguardShowing
+        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        if ((mUnlockMethodCache.canSkipBouncer() || !mKeyguardShowing || mBouncerPreHideAnimation
                 || mKeyguardMonitor.isKeyguardGoingAway()) && !mSimLocked) {
             return STATE_LOCK_OPEN;
         } else if (mTransientBiometricsError) {
@@ -604,7 +617,7 @@
      */
     public void onScrimVisibilityChanged(@ScrimVisibility int scrimsVisible) {
         if (mWakeAndUnlockRunning
-                && scrimsVisible == ScrimController.VISIBILITY_FULLY_TRANSPARENT) {
+                && scrimsVisible == ScrimController.TRANSPARENT) {
             mWakeAndUnlockRunning = false;
             update();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index dcb349b..e34c639 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -75,7 +75,7 @@
         mH = h;
         mWallpaperManager = (WallpaperManager) ctx.getSystemService(Context.WALLPAPER_SERVICE);
         mCurrentUserId = ActivityManager.getCurrentUser();
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
 
         IWallpaperManager service = IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
new file mode 100644
index 0000000..7dcc2fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVPluginManager.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.content.Context
+import android.view.View
+import android.widget.FrameLayout
+import com.android.systemui.plugins.NPVPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.qs.TouchAnimator
+import com.android.systemui.shared.plugins.PluginManager
+
+/**
+ * Manages the NPVPlugin view and state
+ *
+ * Abstracts NPVPlugin from NPV and helps animate on expansion and respond to changes in Config.
+ */
+class NPVPluginManager(
+    var parent: FrameLayout,
+    val pluginManager: PluginManager
+) : PluginListener<NPVPlugin> {
+
+    private var plugin: NPVPlugin? = null
+    private var animator = createAnimator()
+
+    private fun createAnimator() = TouchAnimator.Builder()
+            .addFloat(parent, "alpha", 1f, 0f)
+            .addFloat(parent, "scaleY", 1f, 0f)
+            .build()
+
+    init {
+        pluginManager.addPluginListener(NPVPlugin.ACTION, this, NPVPlugin::class.java, false)
+        parent.pivotY = 0f
+    }
+
+    override fun onPluginConnected(plugin: NPVPlugin, pluginContext: Context) {
+        parent.removeAllViews()
+        plugin.attachToRoot(parent)
+        this.plugin = plugin
+        parent.visibility = View.VISIBLE
+    }
+
+    fun changeVisibility(visibility: Int) {
+        parent.visibility = if (plugin != null) visibility else View.GONE
+    }
+
+    fun destroy() {
+        plugin?.onDestroy()
+        pluginManager.removePluginListener(this)
+    }
+
+    override fun onPluginDisconnected(plugin: NPVPlugin) {
+        if (this.plugin == plugin) {
+            this.plugin = null
+            parent.removeAllViews()
+            parent.visibility = View.GONE
+        }
+    }
+
+    fun setListening(listening: Boolean) {
+        plugin?.setListening(listening)
+    }
+
+    fun setExpansion(expansion: Float, headerTranslation: Float, heightDiff: Float) {
+        parent.setTranslationY(expansion * heightDiff + headerTranslation)
+        if (!expansion.isNaN()) animator.setPosition(expansion)
+    }
+
+    fun replaceFrameLayout(newParent: FrameLayout) {
+        newParent.visibility = parent.visibility
+        parent.removeAllViews()
+        plugin?.attachToRoot(newParent)
+        parent = newParent
+        animator = createAnimator()
+    }
+
+    fun getHeight() = if (plugin != null) parent.height else 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index 195870b..adaea93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -53,12 +54,20 @@
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
     private boolean mIsUpdatingUnchangedGroup;
+    @Nullable private BubbleController mBubbleController = null;
 
     @Inject
     public NotificationGroupManager(StatusBarStateController statusBarStateController) {
         statusBarStateController.addCallback(this);
     }
 
+    private BubbleController getBubbleController() {
+        if (mBubbleController == null) {
+            mBubbleController = Dependency.get(BubbleController.class);
+        }
+        return mBubbleController;
+    }
+
     /**
      * Add a listener for changes to groups.
      *
@@ -187,12 +196,22 @@
         if (group == null) {
             return;
         }
+        int childCount = 0;
+        boolean hasBubbles = false;
+        for (String key : group.children.keySet()) {
+            if (!getBubbleController().isBubbleNotificationSuppressedFromShade(key)) {
+                childCount++;
+            } else {
+                hasBubbles = true;
+            }
+        }
+
         boolean prevSuppressed = group.suppressed;
         group.suppressed = group.summary != null && !group.expanded
-                && (group.children.size() == 1
-                || (group.children.size() == 0
+                && (childCount == 1
+                || (childCount == 0
                         && group.summary.notification.getNotification().isGroupSummary()
-                        && hasIsolatedChildren(group)));
+                        && (hasIsolatedChildren(group) || hasBubbles)));
         if (prevSuppressed != group.suppressed) {
             for (OnGroupChangeListener listener : mListeners) {
                 if (!mIsUpdatingUnchangedGroup) {
@@ -381,6 +400,17 @@
     }
 
     /**
+     * If there is a {@link NotificationGroup} associated with the provided entry, this method
+     * will update the suppression of that group.
+     */
+    public void updateSuppression(NotificationEntry entry) {
+        NotificationGroup group = mGroupMap.get(getGroupKey(entry.notification));
+        if (group != null) {
+            updateSuppression(group);
+        }
+    }
+
+    /**
      * Get the group key. May differ from the one in the notification due to the notification
      * being temporarily isolated.
      *
@@ -565,6 +595,7 @@
                         ? Log.getStackTraceString(child.getDebugThrowable())
                         : "");
             }
+            result += "\n    summary suppressed: " + suppressed;
             return result;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index ba34069..1a3560e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -537,11 +537,7 @@
         if (dozeParameters.shouldControlScreenOff()) {
             mAodIcons.setTranslationY(-mAodIconAppearTranslation);
             mAodIcons.setAlpha(0);
-            mAodIcons.animate()
-                    .setInterpolator(Interpolators.DECELERATE_QUINT)
-                    .translationY(0)
-                    .setDuration(AOD_ICONS_APPEAR_DURATION)
-                    .start();
+            animateInAodIconTranslation();
             mAodIcons.animate()
                     .alpha(1)
                     .setInterpolator(Interpolators.LINEAR)
@@ -550,6 +546,14 @@
         }
     }
 
+    private void animateInAodIconTranslation() {
+        mAodIcons.animate()
+                .setInterpolator(Interpolators.DECELERATE_QUINT)
+                .translationY(0)
+                .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .start();
+    }
+
     private void reloadAodColor() {
         mAodIconTint = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
@@ -606,14 +610,19 @@
                         mAodIcons.setAlpha(1.0f);
                         appearAodIcons();
                     } else {
+                        // Let's make sure the icon are translated to 0, since we cancelled it above
+                        animateInAodIconTranslation();
                         // We were fading out, let's fade in instead
                         CrossFadeHelper.fadeIn(mAodIcons);
                     }
                 } else {
+                    // Let's make sure the icon are translated to 0, since we cancelled it above
+                    animateInAodIconTranslation();
                     CrossFadeHelper.fadeOut(mAodIcons);
                 }
             } else {
                 mAodIcons.setAlpha(1.0f);
+                mAodIcons.setTranslationY(0);
                 mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 248bc75..ea113df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -39,6 +39,7 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.biometrics.BiometricSourceType;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
@@ -62,6 +63,7 @@
 import com.android.keyguard.KeyguardClockSwitch;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
@@ -100,6 +102,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -172,6 +175,46 @@
             R.id.keyguard_hun_animator_start_tag);
     private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
             new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    @VisibleForTesting
+    final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+
+                @Override
+                public void onBiometricAuthenticated(int userId,
+                        BiometricSourceType biometricSourceType) {
+                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        mDelayShowingKeyguardStatusBar = true;
+                    }
+                }
+
+                @Override
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
+                    boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
+                            || mBarState == StatusBarState.SHADE_LOCKED;
+                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+                            && !mDelayShowingKeyguardStatusBar) {
+                        mFirstBypassAttempt = false;
+                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    }
+                }
+
+                @Override
+                public void onFinishedGoingToSleep(int why) {
+                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+                    mDelayShowingKeyguardStatusBar = false;
+                }
+            };
+    private final KeyguardMonitor.Callback mKeyguardMonitorCallback =
+            new KeyguardMonitor.Callback() {
+                @Override
+                public void onKeyguardFadingAwayChanged() {
+                    if (!mKeyguardMonitor.isKeyguardFadingAway()) {
+                        mFirstBypassAttempt = false;
+                        mDelayShowingKeyguardStatusBar = false;
+                    }
+                }
+            };
 
     private final InjectionInflationController mInjectionInflationController;
     private final PowerManager mPowerManager;
@@ -393,6 +436,21 @@
     private boolean mAllowExpandForSmallExpansion;
     private Runnable mExpandAfterLayoutRunnable;
 
+    /**
+     * If face auth with bypass is running for the first time after you turn on the screen.
+     * (From aod or screen off)
+     */
+    private boolean mFirstBypassAttempt;
+    /**
+     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+     * the keyguard is dismissed to show the status bar.
+     */
+    private boolean mDelayShowingKeyguardStatusBar;
+
+    private PluginManager mPluginManager;
+    private FrameLayout mPluginFrame;
+    private NPVPluginManager mNPVPluginManager;
+
     @Inject
     public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
             InjectionInflationController injectionInflationController,
@@ -400,7 +458,8 @@
             PulseExpansionHandler pulseExpansionHandler,
             DynamicPrivacyController dynamicPrivacyController,
             KeyguardBypassController bypassController,
-            FalsingManager falsingManager) {
+            FalsingManager falsingManager,
+            PluginManager pluginManager) {
         super(context, attrs);
         setWillNotDraw(!DEBUG);
         mInjectionInflationController = injectionInflationController;
@@ -421,7 +480,9 @@
         });
         mThemeResId = context.getThemeResId();
         mKeyguardBypassController = bypassController;
-        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+        mKeyguardMonitor.addCallback(mKeyguardMonitorCallback);
         dynamicPrivacyController.addListener(this);
 
         mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
@@ -431,6 +492,7 @@
         });
         mBottomAreaShadeAlphaAnimator.setDuration(160);
         mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+        mPluginManager = pluginManager;
     }
 
     /**
@@ -465,6 +527,9 @@
         mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
         mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
         mLastOrientation = getResources().getConfiguration().orientation;
+        mPluginFrame = findViewById(R.id.plugin_frame);
+        mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+
 
         initBottomArea();
 
@@ -510,6 +575,7 @@
         Dependency.get(StatusBarStateController.class).addCallback(this);
         Dependency.get(ZenModeController.class).addCallback(this);
         Dependency.get(ConfigurationController.class).addCallback(this);
+        mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
         // Theme might have changed between inflating this view and attaching it to the window, so
         // force a call to onThemeChanged
         onThemeChanged();
@@ -522,6 +588,7 @@
         Dependency.get(StatusBarStateController.class).removeCallback(this);
         Dependency.get(ZenModeController.class).removeCallback(this);
         Dependency.get(ConfigurationController.class).removeCallback(this);
+        mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
     }
 
     @Override
@@ -584,6 +651,19 @@
             lp.gravity = panelGravity;
             mNotificationStackScroller.setLayoutParams(lp);
         }
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin =
+                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
     }
 
     @Override
@@ -650,6 +730,43 @@
         if (mOnReinflationListener != null) {
             mOnReinflationListener.run();
         }
+        reinflatePluginContainer();
+    }
+
+    @Override
+    public void onUiModeChanged() {
+        reinflatePluginContainer();
+    }
+
+    private void reinflatePluginContainer() {
+        int index = indexOfChild(mPluginFrame);
+        removeView(mPluginFrame);
+        mPluginFrame = (FrameLayout) mInjectionInflationController
+                .injectable(LayoutInflater.from(mContext)).inflate(
+                        R.layout.status_bar_expanded_plugin_frame,
+                        this,
+                        false);
+        addView(mPluginFrame, index);
+
+        Resources res = getResources();
+        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp;
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin =
+                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+
+        mNPVPluginManager.replaceFrameLayout(mPluginFrame);
     }
 
     private void initBottomArea() {
@@ -679,6 +796,7 @@
         int oldMaxHeight = mQsMaxExpansionHeight;
         if (mQs != null) {
             mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+            mQsMinExpansionHeight += mNPVPluginManager.getHeight();
             mQsMaxExpansionHeight = mQs.getDesiredHeight();
             mNotificationStackScroller.setMaxTopPadding(
                     mQsMaxExpansionHeight + mQsNotificationTopPadding);
@@ -1784,6 +1902,9 @@
                 mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
                         || mQsExpansionFromOverscroll));
         updateEmptyShadeView();
+        mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+                ? View.VISIBLE
+                : View.INVISIBLE);
         mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
                 && !mStackScrollerOverscrolling && mQsScrimEnabled
                 ? View.VISIBLE
@@ -1798,7 +1919,8 @@
     private void setQsExpansion(float height) {
         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
         mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
-        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
+        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
+                && !mDozing) {
             setQsExpanded(true);
         } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
             setQsExpanded(false);
@@ -1839,6 +1961,8 @@
         if (mQs == null) return;
         float qsExpansionFraction = getQsExpansionFraction();
         mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+        mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
         mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
     }
 
@@ -2259,6 +2383,7 @@
                 appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
             }
             startHeight = -mQs.getQsMinExpansionHeight();
+            startHeight -= mNPVPluginManager.getHeight();
         }
         float translation = MathUtils.lerp(startHeight, 0,
                 Math.min(1.0f, appearAmount))
@@ -2299,7 +2424,10 @@
                 * mKeyguardStatusBarAnimateAlpha;
         newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
         mKeyguardStatusBar.setAlpha(newAlpha);
-        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing ? VISIBLE : INVISIBLE);
+        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+                || mDelayShowingKeyguardStatusBar;
+        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
+                ? VISIBLE : INVISIBLE);
     }
 
     private void updateKeyguardBottomAreaAlpha() {
@@ -2399,6 +2527,7 @@
         mKeyguardStatusBar.setListening(listening);
         if (mQs == null) return;
         mQs.setListening(listening);
+        mNPVPluginManager.setListening(listening);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a7262cf..8c92779 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -79,23 +79,24 @@
     /**
      * When both scrims have 0 alpha.
      */
-    public static final int VISIBILITY_FULLY_TRANSPARENT = 0;
+    public static final int TRANSPARENT = 0;
     /**
      * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.)
      */
-    public static final int VISIBILITY_SEMI_TRANSPARENT = 1;
+    public static final int SEMI_TRANSPARENT = 1;
     /**
      * When at least 1 scrim is fully opaque (alpha set to 1.)
      */
-    public static final int VISIBILITY_FULLY_OPAQUE = 2;
+    public static final int OPAQUE = 2;
 
-    @IntDef(prefix = { "VISIBILITY_" }, value = {
-            VISIBILITY_FULLY_TRANSPARENT,
-            VISIBILITY_SEMI_TRANSPARENT,
-            VISIBILITY_FULLY_OPAQUE
+    @IntDef(prefix = {"VISIBILITY_"}, value = {
+            TRANSPARENT,
+            SEMI_TRANSPARENT,
+            OPAQUE
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ScrimVisibility {}
+    public @interface ScrimVisibility {
+    }
 
     /**
      * Default alpha value for most scrims.
@@ -123,8 +124,11 @@
 
     private ScrimState mState = ScrimState.UNINITIALIZED;
     private final Context mContext;
-    protected final ScrimView mScrimBehind;
+
     protected final ScrimView mScrimInFront;
+    protected final ScrimView mScrimBehind;
+    protected final ScrimView mScrimForBubble;
+
     private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DozeParameters mDozeParameters;
@@ -153,10 +157,15 @@
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
-    private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private int mCurrentInFrontTint;
-    private int mCurrentBehindTint;
+
+    private float mInFrontAlpha = NOT_INITIALIZED;
+    private float mBehindAlpha = NOT_INITIALIZED;
+    private float mBubbleAlpha = NOT_INITIALIZED;
+
+    private int mInFrontTint;
+    private int mBehindTint;
+    private int mBubbleTint;
+
     private boolean mWallpaperVisibilityTimedOut;
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
@@ -175,18 +184,21 @@
     private boolean mWakeLockHeld;
     private boolean mKeyguardOccluded;
 
-    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+    public ScrimController(ScrimView scrimBehind, ScrimView scrimInFront, ScrimView scrimForBubble,
             TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
             Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
             AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
+        mScrimForBubble = scrimForBubble;
+
         mScrimStateListener = scrimStateListener;
         mScrimVisibleListener = scrimVisibleListener;
+
         mContext = scrimBehind.getContext();
         mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
         mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
         mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
@@ -213,12 +225,13 @@
 
         final ScrimState[] states = ScrimState.values();
         for (int i = 0; i < states.length; i++) {
-            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].init(mScrimInFront, mScrimBehind, mScrimForBubble, mDozeParameters);
             states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
         }
 
         mScrimBehind.setDefaultFocusHighlightEnabled(false);
         mScrimInFront.setDefaultFocusHighlightEnabled(false);
+        mScrimForBubble.setDefaultFocusHighlightEnabled(false);
 
         updateScrims();
     }
@@ -257,10 +270,14 @@
         mBlankScreen = state.getBlanksScreen();
         mAnimateChange = state.getAnimateChange();
         mAnimationDuration = state.getAnimationDuration();
-        mCurrentInFrontTint = state.getFrontTint();
-        mCurrentBehindTint = state.getBehindTint();
-        mCurrentInFrontAlpha = state.getFrontAlpha();
-        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        mInFrontTint = state.getFrontTint();
+        mBehindTint = state.getBehindTint();
+        mBubbleTint = state.getBubbleTint();
+
+        mInFrontAlpha = state.getFrontAlpha();
+        mBehindAlpha = state.getBehindAlpha();
+        mBubbleAlpha = state.getBubbleAlpha();
         applyExpansionToAlpha();
 
         // Scrim might acquire focus when user is navigating with a D-pad or a keyboard.
@@ -393,21 +410,20 @@
         if (mExpansionFraction != fraction) {
             mExpansionFraction = fraction;
 
-            final boolean keyguardOrUnlocked = mState == ScrimState.UNLOCKED
-                    || mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING;
-            if (!keyguardOrUnlocked || !mExpansionAffectsAlpha) {
+            boolean relevantState = (mState == ScrimState.UNLOCKED
+                    || mState == ScrimState.KEYGUARD
+                    || mState == ScrimState.PULSING
+                    || mState == ScrimState.BUBBLE_EXPANDED);
+            if (!(relevantState && mExpansionAffectsAlpha)) {
                 return;
             }
-
             applyExpansionToAlpha();
-
             if (mUpdatePending) {
                 return;
             }
-
             setOrAdaptCurrentAnimation(mScrimBehind);
             setOrAdaptCurrentAnimation(mScrimInFront);
-
+            setOrAdaptCurrentAnimation(mScrimForBubble);
             dispatchScrimState(mScrimBehind.getViewAlpha());
 
             // Reset wallpaper timeout if it's already timeout like expanding panel while PULSING
@@ -421,11 +437,10 @@
     }
 
     private void setOrAdaptCurrentAnimation(View scrim) {
-        if (!isAnimating(scrim)) {
-            updateScrimColor(scrim, getCurrentScrimAlpha(scrim), getCurrentScrimTint(scrim));
-        } else {
+        float alpha = getCurrentScrimAlpha(scrim);
+        if (isAnimating(scrim)) {
+            // Adapt current animation.
             ValueAnimator previousAnimator = (ValueAnimator) scrim.getTag(TAG_KEY_ANIM);
-            float alpha = getCurrentScrimAlpha(scrim);
             float previousEndValue = (Float) scrim.getTag(TAG_END_ALPHA);
             float previousStartValue = (Float) scrim.getTag(TAG_START_ALPHA);
             float relativeDiff = alpha - previousEndValue;
@@ -433,6 +448,9 @@
             scrim.setTag(TAG_START_ALPHA, newStartValue);
             scrim.setTag(TAG_END_ALPHA, alpha);
             previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+        } else {
+            // Set animation.
+            updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
         }
     }
 
@@ -441,27 +459,27 @@
             return;
         }
 
-        if (mState == ScrimState.UNLOCKED) {
+        if (mState == ScrimState.UNLOCKED || mState == ScrimState.BUBBLE_EXPANDED) {
             // Darken scrim as you pull down the shade when unlocked
             float behindFraction = getInterpolatedFraction();
             behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            mCurrentBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = behindFraction * GRADIENT_SCRIM_ALPHA_BUSY;
+            mInFrontAlpha = 0;
         } else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
             // Either darken of make the scrim transparent when you
             // pull down the shade
             float interpolatedFract = getInterpolatedFraction();
             float alphaBehind = mState.getBehindAlpha();
             if (mDarkenWhileDragging) {
-                mCurrentBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(GRADIENT_SCRIM_ALPHA_BUSY, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             } else {
-                mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
+                mBehindAlpha = MathUtils.lerp(0 /* start */, alphaBehind,
                         interpolatedFract);
-                mCurrentInFrontAlpha = 0;
+                mInFrontAlpha = mState.getFrontAlpha();
             }
-            mCurrentBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
+            mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
                     mState.getBehindTint(), interpolatedFract);
         }
     }
@@ -485,13 +503,14 @@
      * device is dozing when the light sensor is on.
      */
     public void setAodFrontScrimAlpha(float alpha) {
-        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()
-                && mCurrentInFrontAlpha != alpha) {
-            mCurrentInFrontAlpha = alpha;
+        if (((mState == ScrimState.AOD && mDozeParameters.getAlwaysOn())
+                || mState == ScrimState.PULSING) && mInFrontAlpha != alpha) {
+            mInFrontAlpha = alpha;
             updateScrims();
         }
 
         mState.AOD.setAodFrontScrimAlpha(alpha);
+        mState.PULSING.setAodFrontScrimAlpha(alpha);
     }
 
     /**
@@ -499,10 +518,10 @@
      * away once the display turns on.
      */
     public void prepareForGentleWakeUp() {
-        if (mState == ScrimState.AOD) {
-            mCurrentInFrontAlpha = 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+        if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
+            mInFrontAlpha = 1f;
+            mInFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
             mAnimateChange = false;
             updateScrims();
             mAnimateChange = true;
@@ -520,8 +539,8 @@
 
         if (mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -543,8 +562,11 @@
             // Only animate scrim color if the scrim view is actually visible
             boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0 && !mBlankScreen;
             boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0 && !mBlankScreen;
+            boolean animateScrimForBubble = mScrimForBubble.getViewAlpha() != 0 && !mBlankScreen;
+
             mScrimInFront.setColors(mColors, animateScrimInFront);
             mScrimBehind.setColors(mColors, animateScrimBehind);
+            mScrimForBubble.setColors(mColors, animateScrimForBubble);
 
             // Calculate minimum scrim opacity for white or black text.
             int textColor = mColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
@@ -563,12 +585,11 @@
         boolean occludedKeyguard = (mState == ScrimState.PULSING || mState == ScrimState.AOD)
                 && mKeyguardOccluded;
         if (aodWallpaperTimeout || occludedKeyguard) {
-            mCurrentBehindAlpha = 1;
+            mBehindAlpha = 1;
         }
-
-        setScrimInFrontAlpha(mCurrentInFrontAlpha);
-        setScrimBehindAlpha(mCurrentBehindAlpha);
-
+        setScrimAlpha(mScrimInFront, mInFrontAlpha);
+        setScrimAlpha(mScrimBehind, mBehindAlpha);
+        setScrimAlpha(mScrimForBubble, mBubbleAlpha);
         dispatchScrimsVisible();
     }
 
@@ -579,11 +600,11 @@
     private void dispatchScrimsVisible() {
         final int currentScrimVisibility;
         if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) {
-            currentScrimVisibility = VISIBILITY_FULLY_OPAQUE;
+            currentScrimVisibility = OPAQUE;
         } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) {
-            currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT;
+            currentScrimVisibility = TRANSPARENT;
         } else {
-            currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT;
+            currentScrimVisibility = SEMI_TRANSPARENT;
         }
 
         if (mScrimsVisibility != currentScrimVisibility) {
@@ -600,18 +621,10 @@
             return 0;
         } else {
             // woo, special effects
-            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
+            return (float) (1f - 0.5f * (1f - Math.cos(3.14159f * Math.pow(1f - frac, 2f))));
         }
     }
 
-    private void setScrimBehindAlpha(float alpha) {
-        setScrimAlpha(mScrimBehind, alpha);
-    }
-
-    private void setScrimInFrontAlpha(float alpha) {
-        setScrimAlpha(mScrimInFront, alpha);
-    }
-
     private void setScrimAlpha(ScrimView scrim, float alpha) {
         if (alpha == 0f) {
             scrim.setClickable(false);
@@ -622,17 +635,26 @@
         updateScrim(scrim, alpha);
     }
 
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front_scrim";
+        } else if (scrim == mScrimBehind) {
+            return "back_scrim";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble_scrim";
+        }
+        return "unknown_scrim";
+    }
+
     private void updateScrimColor(View scrim, float alpha, int tint) {
         alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_alpha",
                     (int) (alpha * 255));
 
-            Trace.traceCounter(Trace.TRACE_TAG_APP,
-                    scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+            Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
                     Color.alpha(tint));
 
             scrimView.setTint(tint);
@@ -689,9 +711,11 @@
 
     private float getCurrentScrimAlpha(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontAlpha;
+            return mInFrontAlpha;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindAlpha;
+            return mBehindAlpha;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleAlpha;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -699,9 +723,11 @@
 
     private int getCurrentScrimTint(View scrim) {
         if (scrim == mScrimInFront) {
-            return mCurrentInFrontTint;
+            return mInFrontTint;
         } else if (scrim == mScrimBehind) {
-            return mCurrentBehindTint;
+            return mBehindTint;
+        } else if (scrim == mScrimForBubble) {
+            return mBubbleTint;
         } else {
             throw new IllegalArgumentException("Unknown scrim view");
         }
@@ -744,8 +770,9 @@
         // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
         // At the end of the animation we need to remove the tint.
         if (mState == ScrimState.UNLOCKED) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mInFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
         }
     }
 
@@ -850,6 +877,7 @@
 
     /**
      * Executes a callback after the frame has hit the display.
+     *
      * @param callback What to run.
      */
     @VisibleForTesting
@@ -893,16 +921,35 @@
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println(" ScrimController: ");
-        pw.print("  state: "); pw.println(mState);
-        pw.print("  frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
+        pw.print("  state: ");
+        pw.println(mState);
 
-        pw.print("  backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
-        pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
+        pw.print("  frontScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimInFront.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mInFrontAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
-        pw.print("   mTracking="); pw.println(mTracking);
+        pw.print("  backScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimBehind.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBehindAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimBehind.getTint()));
+
+        pw.print("  bubbleScrim:");
+        pw.print(" viewAlpha=");
+        pw.print(mScrimForBubble.getViewAlpha());
+        pw.print(" alpha=");
+        pw.print(mBubbleAlpha);
+        pw.print(" tint=0x");
+        pw.println(Integer.toHexString(mScrimForBubble.getTint()));
+
+        pw.print("   mTracking=");
+        pw.println(mTracking);
     }
 
     public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) {
@@ -949,8 +996,8 @@
         // in this case, back-scrim needs to be re-evaluated
         if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
             float newBehindAlpha = mState.getBehindAlpha();
-            if (mCurrentBehindAlpha != newBehindAlpha) {
-                mCurrentBehindAlpha = newBehindAlpha;
+            if (mBehindAlpha != newBehindAlpha) {
+                mBehindAlpha = newBehindAlpha;
                 updateScrims();
             }
         }
@@ -971,10 +1018,13 @@
     public interface Callback {
         default void onStart() {
         }
+
         default void onDisplayBlanked() {
         }
+
         default void onFinished() {
         }
+
         default void onCancelled() {
         }
         /** Returns whether to timeout wallpaper or not. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 9fdd3b8..7463c7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -36,7 +36,6 @@
      * On the lock screen.
      */
     KEYGUARD(0) {
-
         @Override
         public void prepare(ScrimState previousState) {
             mBlankScreen = false;
@@ -53,10 +52,13 @@
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
             }
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
-            mCurrentInFrontAlpha = 0;
+            mFrontTint = Color.BLACK;
+            mBehindTint = Color.BLACK;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = 0;
+            mBehindAlpha = mScrimBehindAlphaKeyguard;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -66,8 +68,9 @@
     BOUNCER(1) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
-            mCurrentInFrontAlpha = 0f;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mFrontAlpha = 0f;
+            mBubbleAlpha = 0f;
         }
     },
 
@@ -77,8 +80,9 @@
     BOUNCER_SCRIMMED(2) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBehindAlpha = 0;
+            mBubbleAlpha = 0f;
+            mFrontAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
         }
     },
 
@@ -88,8 +92,9 @@
     BRIGHTNESS_MIRROR(3) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
         }
     },
 
@@ -101,9 +106,16 @@
         public void prepare(ScrimState previousState) {
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             mBlankScreen = mDisplayRequiresBlanking;
-            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
-            mCurrentInFrontTint = Color.BLACK;
-            mCurrentBehindTint = Color.BLACK;
+
+            mFrontTint = Color.BLACK;
+            mFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+
+            mBehindTint = Color.BLACK;
+            mBehindAlpha = ScrimController.TRANSPARENT;
+
+            mBubbleTint = Color.TRANSPARENT;
+            mBubbleAlpha = ScrimController.TRANSPARENT;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
             // DisplayPowerManager may blank the screen for us,
             // in this case we just need to set our state.
@@ -127,9 +139,10 @@
     PULSING(5) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontAlpha = 0f;
-            mCurrentBehindTint = Color.BLACK;
-            mCurrentInFrontTint = Color.BLACK;
+            mFrontAlpha = mAodFrontScrimAlpha;
+            mBubbleAlpha = 0f;
+            mBehindTint = Color.BLACK;
+            mFrontTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
             mAnimationDuration = mWakeLockScreenSensorActive
                     ? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -154,25 +167,33 @@
     UNLOCKED(6) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentBehindAlpha = 0;
-            mCurrentInFrontAlpha = 0;
+            // State that UI will sync to.
+            mBehindAlpha = 0;
+            mFrontAlpha = 0;
+            mBubbleAlpha = 0;
+
             mAnimationDuration = mKeyguardFadingAway
                     ? mKeyguardFadingAwayDuration
                     : StatusBar.FADE_KEYGUARD_DURATION;
+
             mAnimateChange = !mLaunchingAffordanceWithPreview;
 
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+            mBlankScreen = false;
+
             if (previousState == ScrimState.AOD) {
-                // Fade from black to transparent when coming directly from AOD
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
-                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Set all scrims black, before they fade transparent.
+                updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+                updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+
                 // Scrims should still be black at the end of the transition.
-                mCurrentInFrontTint = Color.BLACK;
-                mCurrentBehindTint = Color.BLACK;
+                mFrontTint = Color.BLACK;
+                mBehindTint = Color.BLACK;
+                mBubbleTint = Color.BLACK;
                 mBlankScreen = true;
-            } else {
-                mCurrentInFrontTint = Color.TRANSPARENT;
-                mCurrentBehindTint = Color.TRANSPARENT;
-                mBlankScreen = false;
             }
         }
     },
@@ -183,25 +204,36 @@
     BUBBLE_EXPANDED(7) {
         @Override
         public void prepare(ScrimState previousState) {
-            mCurrentInFrontTint = Color.TRANSPARENT;
-            mCurrentBehindTint = Color.TRANSPARENT;
+            mFrontTint = Color.TRANSPARENT;
+            mBehindTint = Color.TRANSPARENT;
+            mBubbleTint = Color.TRANSPARENT;
+
+            mFrontAlpha = ScrimController.TRANSPARENT;
+            mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+            mBubbleAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
+
             mAnimationDuration = ScrimController.ANIMATION_DURATION;
-            mCurrentBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
             mBlankScreen = false;
         }
     };
 
     boolean mBlankScreen = false;
     long mAnimationDuration = ScrimController.ANIMATION_DURATION;
-    int mCurrentInFrontTint = Color.TRANSPARENT;
-    int mCurrentBehindTint = Color.TRANSPARENT;
+    int mFrontTint = Color.TRANSPARENT;
+    int mBehindTint = Color.TRANSPARENT;
+    int mBubbleTint = Color.TRANSPARENT;
+
     boolean mAnimateChange = true;
-    float mCurrentInFrontAlpha;
-    float mCurrentBehindAlpha;
     float mAodFrontScrimAlpha;
+    float mFrontAlpha;
+    float mBehindAlpha;
+    float mBubbleAlpha;
+
     float mScrimBehindAlphaKeyguard;
     ScrimView mScrimInFront;
     ScrimView mScrimBehind;
+    ScrimView mScrimForBubble;
+
     DozeParameters mDozeParameters;
     boolean mDisplayRequiresBlanking;
     boolean mWallpaperSupportsAmbientMode;
@@ -216,13 +248,17 @@
         mIndex = index;
     }
 
-    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
+            DozeParameters dozeParameters) {
         mScrimInFront = scrimInFront;
         mScrimBehind = scrimBehind;
+        mScrimForBubble = scrimForBubble;
+
         mDozeParameters = dozeParameters;
         mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking();
     }
 
+    /** Prepare state for transition. */
     public void prepare(ScrimState previousState) {
     }
 
@@ -231,19 +267,27 @@
     }
 
     public float getFrontAlpha() {
-        return mCurrentInFrontAlpha;
+        return mFrontAlpha;
     }
 
     public float getBehindAlpha() {
-        return mCurrentBehindAlpha;
+        return mBehindAlpha;
+    }
+
+    public float getBubbleAlpha() {
+        return mBubbleAlpha;
     }
 
     public int getFrontTint() {
-        return mCurrentInFrontTint;
+        return mFrontTint;
     }
 
     public int getBehindTint() {
-        return mCurrentBehindTint;
+        return mBehindTint;
+    }
+
+    public int getBubbleTint() {
+        return mBubbleTint;
     }
 
     public long getAnimationDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 9fc3e47..7bc849d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -701,7 +701,7 @@
                 mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
 
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -824,6 +824,7 @@
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mZenController.addCallback(this);
         NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
@@ -944,8 +945,10 @@
 
         ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
         ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimForBubble = mStatusBarWindow.findViewById(R.id.scrim_for_bubble);
+
         mScrimController = SystemUIFactory.getInstance().createScrimController(
-                scrimBehind, scrimInFront, mLockscreenWallpaper,
+                scrimBehind, scrimInFront, scrimForBubble, mLockscreenWallpaper,
                 (state, alpha, color) -> mLightBarController.setScrimState(state, alpha, color),
                 scrimsVisible -> {
                     if (mStatusBarWindowController != null) {
@@ -1260,7 +1263,7 @@
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                 getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
                 mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
-                mKeyguardBypassController, mFalsingManager);
+                mKeyguardBypassController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -2417,6 +2420,10 @@
             pw.println("  mGroupManager: null");
         }
 
+        if (mBubbleController != null) {
+            mBubbleController.dump(fd, pw, args);
+        }
+
         if (mLightBarController != null) {
             mLightBarController.dump(fd, pw, args);
         }
@@ -4532,6 +4539,14 @@
     }
 
     /**
+     * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
+     */
+    public void onBouncerPreHideAnimation() {
+        mNotificationPanel.onBouncerPreHideAnimation();
+        mStatusBarWindow.onBouncerPreHideAnimation();
+    }
+
+    /**
      * @return a PackageManger for userId or if userId is < 0 (USER_ALL etc) then
      *         return PackageManager for mContext
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ccb85fa..bb8ba05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -193,7 +193,7 @@
         mViewMediatorCallback = callback;
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
-        KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
+        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
         Dependency.get(ConfigurationController.class).addCallback(this);
         mGesturalNav = QuickStepContract.isGesturalMode(
@@ -279,14 +279,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 */);
             }
         }
     }
@@ -395,7 +396,7 @@
             } else {
                 showBouncerOrKeyguard(hideBouncerWhenShowing);
             }
-            KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
+            Dependency.get(KeyguardUpdateMonitor.class).sendKeyguardReset();
             updateStates();
         }
     }
@@ -516,7 +517,7 @@
     public void startPreHideAnimation(Runnable finishRunnable) {
         if (mBouncer.isShowing()) {
             mBouncer.startPreHideAnimation(finishRunnable);
-            mNotificationPanelView.onBouncerPreHideAnimation();
+            mStatusBar.onBouncerPreHideAnimation();
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
@@ -533,7 +534,7 @@
                 mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded());
         launchPendingWakeupAction();
 
-        if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
+        if (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
             fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
         }
         long uptimeMillis = SystemClock.uptimeMillis();
@@ -785,7 +786,7 @@
             updateLockIcon();
         }
 
-        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        KeyguardUpdateMonitor updateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
             updateMonitor.onKeyguardVisibilityChanged(showing && !occluded);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index bec53a1..320243b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -411,7 +411,7 @@
                 if (DEBUG) {
                     Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.key);
                 }
-            } else if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
+            } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
                 if (DEBUG) {
                     Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.key);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a870590..4732049 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -224,7 +224,7 @@
             mVisualStabilityManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(this,
                     notifListContainer, mCheckSaveListener, mOnSettingsClickListener);
-            // ForegroundServiceControllerListener adds its listener in its constructor
+            // ForegroundServiceNotificationListener adds its listener in its constructor
             // but we need to request it here in order for it to be instantiated.
             // TODO: figure out how to do this correctly once Dependency.get() is gone.
             Dependency.get(ForegroundServiceNotificationListener.class);
@@ -240,7 +240,7 @@
     public void onDensityOrFontScaleChanged() {
         MessagingMessage.dropCache();
         MessagingGroup.dropCache();
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+        if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
             updateNotificationsOnDensityOrFontScaleChanged();
         } else {
             mReinflateNotificationsOnUserSwitched = true;
@@ -249,7 +249,7 @@
 
     @Override
     public void onUiModeChanged() {
-        if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
+        if (!Dependency.get(KeyguardUpdateMonitor.class).isSwitchingUser()) {
             updateNotificationOnUiModeChanged();
         } else {
             mDispatchUiModeChangeOnUserSwitched = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index e85b147..724b462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -40,9 +40,9 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.R;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -223,7 +223,7 @@
         }
 
         final boolean scrimsOccludingWallpaper =
-                state.scrimsVisibility == ScrimController.VISIBILITY_FULLY_OPAQUE;
+                state.scrimsVisibility == ScrimController.OPAQUE;
         final boolean keyguardOrAod = state.keyguardShowing
                 || (state.dozing && mDozeParameters.getAlwaysOn());
         if (keyguardOrAod && !state.backdropShowing && !scrimsOccludingWallpaper) {
@@ -309,7 +309,7 @@
         return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded()
                 || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
                 || state.headsUpShowing || state.bubblesShowing
-                || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT);
+                || state.scrimsVisibility != ScrimController.TRANSPARENT);
     }
 
     private void applyFitsSystemWindows(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 7a81ed4..a9e818d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -527,6 +527,15 @@
         }
     }
 
+    /**
+     * When {@link KeyguardBouncer} starts to be dismissed and starts to play its animation.
+     */
+    public void onBouncerPreHideAnimation() {
+        if (mLockIcon != null) {
+            mLockIcon.onBouncerPreHideAnimation();
+        }
+    }
+
     public class LayoutParams extends FrameLayout.LayoutParams {
 
         public boolean ignoreRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index 24ecd14..c76f93e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -28,6 +28,7 @@
 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;
@@ -57,8 +58,8 @@
 
     private UnlockMethodCache(Context ctx) {
         mLockPatternUtils = new LockPatternUtils(ctx);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(ctx);
-        KeyguardUpdateMonitor.getInstance(ctx).registerCallback(mCallback);
+        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
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 40c3d9d..0a2fb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.policy;
 
 import static com.android.systemui.Dependency.BG_LOOPER_NAME;
+import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -67,16 +68,18 @@
     private boolean mEnabled;
     private int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
 
-    private final H mHandler = new H(Looper.getMainLooper());
+    private final H mHandler;
     private int mState;
 
     /**
      */
     @Inject
     public BluetoothControllerImpl(Context context, @Named(BG_LOOPER_NAME) Looper bgLooper,
+            @Named(MAIN_LOOPER_NAME) Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager) {
         mLocalBluetoothManager = localBluetoothManager;
         mBgHandler = new Handler(bgLooper);
+        mHandler = new H(mainLooper);
         if (mLocalBluetoothManager != null) {
             mLocalBluetoothManager.getEventManager().registerCallback(this);
             mLocalBluetoothManager.getProfileManager().addServiceListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index f2c0434..353d6a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -32,6 +32,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.Dependency;
 
 import java.util.List;
 
@@ -66,7 +67,7 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
         mKeyguardUpdateMonitor.registerCallback(mCallback);
         getContext().registerReceiver(mReceiver,
                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index ee78a72..be27741 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -80,6 +80,7 @@
      */
     public static InflatedSmartReplies inflate(
             Context context,
+            Context packageContext,
             NotificationEntry entry,
             SmartReplyConstants smartReplyConstants,
             SmartReplyController smartReplyController,
@@ -108,9 +109,9 @@
         }
         if (newSmartRepliesAndActions.smartActions != null) {
             suggestionButtons.addAll(
-                    smartReplyView.inflateSmartActions(newSmartRepliesAndActions.smartActions,
-                            smartReplyController, entry, headsUpManager,
-                            delayOnClickListener));
+                    smartReplyView.inflateSmartActions(packageContext,
+                            newSmartRepliesAndActions.smartActions, smartReplyController, entry,
+                            headsUpManager, delayOnClickListener));
         }
 
         return new InflatedSmartReplies(smartReplyView, suggestionButtons,
@@ -123,7 +124,7 @@
         if (left == right) return true;
         if (left == null || right == null) return false;
 
-        if (!Arrays.equals(left.getSmartReplies(), right.getSmartReplies())) {
+        if (!left.getSmartReplies().equals(right.getSmartReplies())) {
             return false;
         }
 
@@ -199,7 +200,7 @@
         SmartReplyView.SmartActions smartActions = null;
         if (appGeneratedSmartRepliesExist) {
             smartReplies = new SmartReplyView.SmartReplies(
-                    remoteInputActionPair.first.getChoices(),
+                    Arrays.asList(remoteInputActionPair.first.getChoices()),
                     remoteInputActionPair.first,
                     remoteInputActionPair.second.actionIntent,
                     false /* fromAssistant */);
@@ -210,22 +211,22 @@
         }
         // Apps didn't provide any smart replies / actions, use those from NAS (if any).
         if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
-            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.systemGeneratedSmartReplies)
+            boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.getSmartReplies())
                     && freeformRemoteInputActionPair != null
                     && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
                     && freeformRemoteInputActionPair.second.actionIntent != null;
             if (useGeneratedReplies) {
                 smartReplies = new SmartReplyView.SmartReplies(
-                        entry.systemGeneratedSmartReplies,
+                        entry.getSmartReplies(),
                         freeformRemoteInputActionPair.first,
                         freeformRemoteInputActionPair.second.actionIntent,
                         true /* fromAssistant */);
             }
-            boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions)
+            boolean useSmartActions = !ArrayUtils.isEmpty(entry.getSmartActions())
                     && notification.getAllowSystemGeneratedContextualActions();
             if (useSmartActions) {
                 List<Notification.Action> systemGeneratedActions =
-                        entry.systemGeneratedSmartActions;
+                        entry.getSmartActions();
                 // Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
                 // since notifications aren't shown there anyway.
                 ActivityManagerWrapper activityManagerWrapper =
@@ -288,8 +289,8 @@
             this.smartActions = smartActions;
         }
 
-        @NonNull public CharSequence[] getSmartReplies() {
-            return smartReplies == null ? new CharSequence[0] : smartReplies.choices;
+        @NonNull public List<CharSequence> getSmartReplies() {
+            return smartReplies == null ? Collections.emptyList() : smartReplies.choices;
         }
 
         @NonNull public List<Notification.Action> getSmartActions() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index e1ef809..6dc90b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -19,7 +19,6 @@
 public interface KeyguardMonitor extends CallbackController<Callback> {
 
     boolean isSecure();
-    boolean canSkipBouncer();
     boolean isShowing();
     boolean isOccluded();
     boolean isKeyguardFadingAway();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index 87ed14a..e8b0f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -17,13 +17,12 @@
 package com.android.systemui.statusbar.policy;
 
 import android.annotation.NonNull;
-import android.app.ActivityManager;
 import android.content.Context;
 
 import com.android.internal.util.Preconditions;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.Dependency;
 
 import java.util.ArrayList;
 
@@ -39,14 +38,11 @@
     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     private final Context mContext;
-    private final CurrentUserTracker mUserTracker;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
-    private int mCurrentUser;
     private boolean mShowing;
     private boolean mSecure;
     private boolean mOccluded;
-    private boolean mCanSkipBouncer;
 
     private boolean mListening;
     private boolean mKeyguardFadingAway;
@@ -61,14 +57,7 @@
     @Inject
     public KeyguardMonitorImpl(Context context) {
         mContext = context;
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mUserTracker = new CurrentUserTracker(mContext) {
-            @Override
-            public void onUserSwitched(int newUserId) {
-                mCurrentUser = newUserId;
-                updateCanSkipBouncerState();
-            }
-        };
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
     }
 
     @Override
@@ -77,10 +66,7 @@
         mCallbacks.add(callback);
         if (mCallbacks.size() != 0 && !mListening) {
             mListening = true;
-            mCurrentUser = ActivityManager.getCurrentUser();
-            updateCanSkipBouncerState();
             mKeyguardUpdateMonitor.registerCallback(this);
-            mUserTracker.startTracking();
         }
     }
 
@@ -90,7 +76,6 @@
         if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
             mListening = false;
             mKeyguardUpdateMonitor.removeCallback(this);
-            mUserTracker.stopTracking();
         }
     }
 
@@ -109,11 +94,6 @@
         return mOccluded;
     }
 
-    @Override
-    public boolean canSkipBouncer() {
-        return mCanSkipBouncer;
-    }
-
     public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
         if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
         mShowing = showing;
@@ -124,7 +104,6 @@
 
     @Override
     public void onTrustChanged(int userId) {
-        updateCanSkipBouncerState();
         notifyKeyguardChanged();
     }
 
@@ -132,10 +111,6 @@
         return mKeyguardUpdateMonitor.isDeviceInteractive();
     }
 
-    private void updateCanSkipBouncerState() {
-        mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
-    }
-
     private void notifyKeyguardChanged() {
         // Copy the list to allow removal during callback.
         new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 7acf4fd..5da7267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -21,6 +21,7 @@
 import android.net.NetworkCapabilities;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
 import android.provider.Settings.Global;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneStateListener;
@@ -54,6 +55,10 @@
 
 public class MobileSignalController extends SignalController<
         MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
+
+    // The message to display Nr5G icon gracfully by CarrierConfig timeout
+    private static final int MSG_DISPLAY_GRACE = 1;
+
     private final TelephonyManager mPhone;
     private final SubscriptionDefaults mDefaults;
     private final String mNetworkNameDefault;
@@ -76,8 +81,11 @@
     private SignalStrength mSignalStrength;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
+    private final Handler mDisplayGraceHandler;
     @VisibleForTesting
     boolean mInflateSignalStrengths = false;
+    @VisibleForTesting
+    boolean mIsShowingIconGracefully = false;
     // Some specific carriers have 5GE network which is special LTE CA network.
     private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1;
 
@@ -116,6 +124,16 @@
                 updateTelephony();
             }
         };
+
+        mDisplayGraceHandler = new Handler(receiverLooper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_DISPLAY_GRACE) {
+                    mIsShowingIconGracefully = false;
+                    updateTelephony();
+                }
+            }
+        };
     }
 
     public void setConfiguration(Config config) {
@@ -276,7 +294,8 @@
             }
             boolean dataDisabled = mCurrentState.userSetup
                     && (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
-                    || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA);
+                    || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA
+                            && mCurrentState.defaultDataOff));
             boolean noInternet = mCurrentState.inetCondition == 0;
             boolean cutOut = dataDisabled || noInternet;
             return SignalDrawable.getState(level, getNumLevels(), cutOut);
@@ -302,7 +321,7 @@
             dataContentDescription = mContext.getString(R.string.data_connection_no_internet);
         }
         final boolean dataDisabled = (mCurrentState.iconGroup == TelephonyIcons.DATA_DISABLED
-                || mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA)
+                || (mCurrentState.iconGroup == TelephonyIcons.NOT_DEFAULT_DATA))
                 && mCurrentState.userSetup;
 
         // Show icon in QS when we are connected or data is disabled.
@@ -466,6 +485,7 @@
             Log.d(mTag, "updateTelephonySignalStrength: hasService=" +
                     Utils.isInService(mServiceState) + " ss=" + mSignalStrength);
         }
+        checkDefaultData();
         mCurrentState.connected = Utils.isInService(mServiceState)
                 && mSignalStrength != null;
         if (mCurrentState.connected) {
@@ -479,6 +499,10 @@
         // When the device is camped on a 5G Non-Standalone network, the data network type is still
         // LTE. In this case, we first check which 5G icon should be shown.
         MobileIconGroup nr5GIconGroup = getNr5GIconGroup();
+        if (mConfig.nrIconDisplayGracePeriodMs > 0) {
+            nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup);
+        }
+
         if (nr5GIconGroup != null) {
             mCurrentState.iconGroup = nr5GIconGroup;
         } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
@@ -519,6 +543,23 @@
         notifyListenersIfNecessary();
     }
 
+    /**
+     * If we are controlling the NOT_DEFAULT_DATA icon, check the status of the other one
+     */
+    private void checkDefaultData() {
+        if (mCurrentState.iconGroup != TelephonyIcons.NOT_DEFAULT_DATA) {
+            mCurrentState.defaultDataOff = false;
+            return;
+        }
+
+        mCurrentState.defaultDataOff = mNetworkController.isDataControllerDisabled();
+    }
+
+    void onMobileDataChanged() {
+        checkDefaultData();
+        notifyListenersIfNecessary();
+    }
+
     private MobileIconGroup getNr5GIconGroup() {
         if (mServiceState == null) return null;
 
@@ -555,7 +596,47 @@
         return null;
     }
 
-    private boolean isDataDisabled() {
+    /**
+     * The function to adjust MobileIconGroup depend on CarrierConfig's time
+     * nextIconGroup == null imply next state could be 2G/3G/4G/4G+
+     * nextIconGroup != null imply next state will be 5G/5G+
+     * Flag : mIsShowingIconGracefully
+     * ---------------------------------------------------------------------------------
+     * |   Last state   |  Current state  | Flag |       Action                        |
+     * ---------------------------------------------------------------------------------
+     * |     5G/5G+     | 2G/3G/4G/4G+    | true | return previous IconGroup           |
+     * |     5G/5G+     |     5G/5G+      | true | Bypass                              |
+     * |  2G/3G/4G/4G+  |     5G/5G+      | true | Bypass                              |
+     * |  2G/3G/4G/4G+  | 2G/3G/4G/4G+    | true | Bypass                              |
+     * |  SS.connected  | SS.disconnect   |  T|F | Reset timer                         |
+     * |NETWORK_TYPE_LTE|!NETWORK_TYPE_LTE|  T|F | Reset timer                         |
+     * |     5G/5G+     | 2G/3G/4G/4G+    | false| Bypass                              |
+     * |     5G/5G+     |     5G/5G+      | false| Bypass                              |
+     * |  2G/3G/4G/4G+  |     5G/5G+      | false| SendMessageDelay(time), flag->true  |
+     * |  2G/3G/4G/4G+  | 2G/3G/4G/4G+    | false| Bypass                              |
+     * ---------------------------------------------------------------------------------
+     */
+    private MobileIconGroup adjustNr5GIconGroupByDisplayGraceTime(
+            MobileIconGroup candidateIconGroup) {
+        if (mIsShowingIconGracefully && candidateIconGroup == null) {
+            candidateIconGroup = (MobileIconGroup) mCurrentState.iconGroup;
+        } else if (!mIsShowingIconGracefully && candidateIconGroup != null
+                && mLastState.iconGroup != candidateIconGroup) {
+            mDisplayGraceHandler.sendMessageDelayed(
+                    mDisplayGraceHandler.obtainMessage(MSG_DISPLAY_GRACE),
+                    mConfig.nrIconDisplayGracePeriodMs);
+            mIsShowingIconGracefully = true;
+        } else if (!mCurrentState.connected || mDataState == TelephonyManager.DATA_DISCONNECTED
+                || candidateIconGroup == null) {
+            mDisplayGraceHandler.removeMessages(MSG_DISPLAY_GRACE);
+            mIsShowingIconGracefully = false;
+            candidateIconGroup = null;
+        }
+
+        return candidateIconGroup;
+    }
+
+    boolean isDataDisabled() {
         return !mPhone.isDataCapable();
     }
 
@@ -580,6 +661,7 @@
         pw.println("  mDataNetType=" + mDataNetType + ",");
         pw.println("  mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
         pw.println("  isDataDisabled=" + isDataDisabled() + ",");
+        pw.println("  mIsShowingIconGracefully=" + mIsShowingIconGracefully + ",");
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
@@ -687,6 +769,7 @@
         boolean isDefault;
         boolean userSetup;
         boolean roaming;
+        boolean defaultDataOff;  // Tracks the on/off state of the defaultDataSubscription
 
         @Override
         public void copyFrom(State s) {
@@ -702,6 +785,7 @@
             carrierNetworkChangeMode = state.carrierNetworkChangeMode;
             userSetup = state.userSetup;
             roaming = state.roaming;
+            defaultDataOff = state.defaultDataOff;
         }
 
         @Override
@@ -718,7 +802,8 @@
             builder.append("airplaneMode=").append(airplaneMode).append(',');
             builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode)
                     .append(',');
-            builder.append("userSetup=").append(userSetup);
+            builder.append("userSetup=").append(userSetup).append(',');
+            builder.append("defaultDataOff=").append(defaultDataOff);
         }
 
         @Override
@@ -733,7 +818,8 @@
                     && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
                     && ((MobileState) o).userSetup == userSetup
                     && ((MobileState) o).isDefault == isDefault
-                    && ((MobileState) o).roaming == roaming;
+                    && ((MobileState) o).roaming == roaming
+                    && ((MobileState) o).defaultDataOff == defaultDataOff;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 292571e..7b5d489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -51,6 +51,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.SparseArray;
@@ -219,6 +220,7 @@
             @Override
             public void onMobileDataEnabled(boolean enabled) {
                 mCallbackHandler.setMobileDataEnabled(enabled);
+                notifyControllersMobileDataChanged();
             }
         });
         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
@@ -385,6 +387,22 @@
         return mMobileSignalControllers.size();
     }
 
+    boolean isDataControllerDisabled() {
+        MobileSignalController dataController = getDataController();
+        if (dataController == null) {
+            return false;
+        }
+
+        return dataController.isDataDisabled();
+    }
+
+    private void notifyControllersMobileDataChanged() {
+        for (int i = 0; i < mMobileSignalControllers.size(); i++) {
+            MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
+            mobileSignalController.onMobileDataChanged();
+        }
+    }
+
     public boolean isEmergencyOnly() {
         if (mMobileSignalControllers.size() == 0) {
             // When there are no active subscriptions, determine emengency state from last
@@ -833,6 +851,13 @@
         pw.print("  mEmergencySource=");
         pw.println(emergencyToString(mEmergencySource));
 
+        pw.println("  - config ------");
+        pw.print("  patternOfCarrierSpecificDataIcon=");
+        pw.println(mConfig.patternOfCarrierSpecificDataIcon);
+        pw.print("  nr5GIconMap=");
+        pw.println(mConfig.nr5GIconMap.toString());
+        pw.print("  nrIconDisplayGracePeriodMs=");
+        pw.println(mConfig.nrIconDisplayGracePeriodMs);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
             MobileSignalController mobileSignalController = mMobileSignalControllers.valueAt(i);
             mobileSignalController.dump(pw);
@@ -996,6 +1021,8 @@
                             datatype.equals("3g") ? TelephonyIcons.THREE_G :
                             datatype.equals("4g") ? TelephonyIcons.FOUR_G :
                             datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
+                            datatype.equals("5g") ? TelephonyIcons.NR_5G :
+                            datatype.equals("5g+") ? TelephonyIcons.NR_5G_PLUS :
                             datatype.equals("e") ? TelephonyIcons.E :
                             datatype.equals("g") ? TelephonyIcons.G :
                             datatype.equals("h") ? TelephonyIcons.H :
@@ -1127,6 +1154,7 @@
         boolean inflateSignalStrengths = false;
         boolean alwaysShowDataRatIcon = false;
         public String patternOfCarrierSpecificDataIcon = "";
+        public long nrIconDisplayGracePeriodMs;
 
         /**
          * Mapping from NR 5G status string to an integer. The NR 5G status string should match
@@ -1179,6 +1207,9 @@
                         add5GIconMapping(pair, config);
                     }
                 }
+                setDisplayGraceTime(
+                        b.getInt(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT),
+                        config);
             }
 
             return config;
@@ -1212,5 +1243,18 @@
                         TelephonyIcons.ICON_NAME_TO_ICON.get(value));
             }
         }
+
+        /**
+         * Set display gracefully period time(MS) depend on carrierConfig KEY
+         * KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT, and this function will convert to ms.
+         * {@link CarrierConfigManager}.
+         *
+         * @param time   showing 5G icon gracefully in the period of the time(SECOND)
+         * @param config container that used to store the parsed configs.
+         */
+        @VisibleForTesting
+        static void setDisplayGraceTime(int time, Config config) {
+            config.nrIconDisplayGracePeriodMs = time * DateUtils.SECOND_IN_MILLIS;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 282d28c..b5f660a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -220,7 +220,7 @@
 
         if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
             if (smartReplies.choices != null) {
-                for (int i = 0; i < smartReplies.choices.length; ++i) {
+                for (int i = 0; i < smartReplies.choices.size(); ++i) {
                     buttons.add(inflateReplyButton(
                             this, getContext(), i, smartReplies, smartReplyController, entry,
                             delayOnClickListener));
@@ -235,17 +235,17 @@
      * Add smart actions to be shown next to smart replies. Only the actions that fit into the
      * notification are shown.
      */
-    public List<Button> inflateSmartActions(@NonNull SmartActions smartActions,
-            SmartReplyController smartReplyController, NotificationEntry entry,
-            HeadsUpManager headsUpManager, boolean delayOnClickListener) {
+    public List<Button> inflateSmartActions(Context packageContext,
+            @NonNull SmartActions smartActions, SmartReplyController smartReplyController,
+            NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
         List<Button> buttons = new ArrayList<>();
         int numSmartActions = smartActions.actions.size();
         for (int n = 0; n < numSmartActions; n++) {
             Notification.Action action = smartActions.actions.get(n);
             if (action.actionIntent != null) {
                 buttons.add(inflateActionButton(
-                        this, getContext(), n, smartActions, smartReplyController, entry,
-                        headsUpManager, delayOnClickListener));
+                        this, getContext(), packageContext, n, smartActions, smartReplyController,
+                        entry, headsUpManager, delayOnClickListener));
             }
         }
         return buttons;
@@ -265,7 +265,7 @@
             NotificationEntry entry, boolean useDelayedOnClickListener) {
         Button b = (Button) LayoutInflater.from(context).inflate(
                 R.layout.smart_reply_button, smartReplyView, false);
-        CharSequence choice = smartReplies.choices[replyIndex];
+        CharSequence choice = smartReplies.choices.get(replyIndex);
         b.setText(choice);
 
         OnDismissAction action = () -> {
@@ -327,7 +327,7 @@
 
     @VisibleForTesting
     static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
-            int actionIndex, SmartActions smartActions,
+            Context packageContext, int actionIndex, SmartActions smartActions,
             SmartReplyController smartReplyController, NotificationEntry entry,
             HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
         Notification.Action action = smartActions.actions.get(actionIndex);
@@ -335,7 +335,9 @@
                 R.layout.smart_action_button, smartReplyView, false);
         button.setText(action.title);
 
-        Drawable iconDrawable = action.getIcon().loadDrawable(context);
+        // We received the Icon from the application - so use the Context of the application to
+        // reference icon resources.
+        Drawable iconDrawable = action.getIcon().loadDrawable(packageContext);
         // Add the action icon to the Smart Action button.
         int newIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.smart_action_button_icon_size);
@@ -944,10 +946,10 @@
         @NonNull
         public final PendingIntent pendingIntent;
         @NonNull
-        public final CharSequence[] choices;
+        public final List<CharSequence> choices;
         public final boolean fromAssistant;
 
-        public SmartReplies(CharSequence[] choices, RemoteInput remoteInput,
+        public SmartReplies(List<CharSequence> choices, RemoteInput remoteInput,
                 PendingIntent pendingIntent, boolean fromAssistant) {
             this.choices = choices;
             this.remoteInput = remoteInput;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 2e1e520..4fa4b6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -62,6 +62,7 @@
 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;
@@ -597,17 +598,19 @@
 
         final UserSwitcherController mController;
         private final KeyguardMonitor mKeyguardMonitor;
+        private final UnlockMethodCache mUnlockMethodCache;
 
         protected BaseUserAdapter(UserSwitcherController controller) {
             mController = controller;
             mKeyguardMonitor = controller.mKeyguardMonitor;
+            mUnlockMethodCache = UnlockMethodCache.getInstance(controller.mContext);
             controller.addAdapter(new WeakReference<>(this));
         }
 
         public int getUserCount() {
             boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
                     && mKeyguardMonitor.isSecure()
-                    && !mKeyguardMonitor.canSkipBouncer();
+                    && !mUnlockMethodCache.canSkipBouncer();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
@@ -629,7 +632,7 @@
         public int getCount() {
             boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
                     && mKeyguardMonitor.isSecure()
-                    && !mKeyguardMonitor.canSkipBouncer();
+                    && !mUnlockMethodCache.canSkipBouncer();
             if (!secureKeyguardShowing) {
                 return mController.getUsers().size();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 1d4f9b3..453c2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,12 +33,19 @@
 import com.android.systemui.R;
 import com.android.systemui.fragments.FragmentService;
 
+import javax.inject.Inject;
+
 public class TunerActivity extends Activity implements
         PreferenceFragment.OnPreferenceStartFragmentCallback,
         PreferenceFragment.OnPreferenceStartScreenCallback {
 
     private static final String TAG_TUNER = "tuner";
 
+    @Inject
+    TunerActivity() {
+        super();
+    }
+
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -50,8 +57,6 @@
             setActionBar(toolbar);
         }
 
-        Dependency.initDependencies(this);
-
         if (getFragmentManager().findFragmentByTag(TAG_TUNER) == null) {
             final String action = getIntent().getAction();
             boolean showDemoMode = action != null && action.equals(
@@ -67,7 +72,6 @@
     protected void onDestroy() {
         super.onDestroy();
         Dependency.destroy(FragmentService.class, s -> s.destroyAll());
-        Dependency.clearDependencies();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 9b264c4..e44e58a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -32,6 +32,7 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -172,6 +173,11 @@
          * Creates the QuickQSPanel.
          */
         QuickQSPanel createQuickQSPanel();
+
+        /**
+         * Creates the QSCustomizer.
+         */
+        QSCustomizer createQSCustomizer();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
rename to packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index b9c5ee5..dcd0c58 100644
--- a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
 
 import android.content.Context;
 import android.hardware.HardwareBuffer;
@@ -56,23 +56,31 @@
 
     private final SensorManager mInner;
     private final List<Sensor> mSensorCache;
-    private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
-    @VisibleForTesting final Handler mHandler;
+    private final Handler mHandler;
     private final List<SensorManagerPlugin> mPlugins;
 
     @Inject
     public AsyncSensorManager(Context context, PluginManager pluginManager) {
-        this(context.getSystemService(SensorManager.class), pluginManager);
+        this(context.getSystemService(SensorManager.class), pluginManager, null);
     }
 
     @VisibleForTesting
-    AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) {
+    public AsyncSensorManager(
+            SensorManager sensorManager, PluginManager pluginManager, Handler handler) {
         mInner = sensorManager;
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        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<>();
-        pluginManager.addPluginListener(this, SensorManagerPlugin.class, true /* allowMultiple */);
+        if (pluginManager != null) {
+            pluginManager.addPluginListener(this, SensorManagerPlugin.class,
+                    true /* allowMultiple */);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
new file mode 100644
index 0000000..cce5bca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.sensors;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Simple wrapper around SensorManager customized for the Proximity sensor.
+ */
+public class ProximitySensor {
+    private static final String TAG = "ProxSensor";
+    private static final boolean DEBUG = false;
+
+    private final Sensor mSensor;
+    private final AsyncSensorManager mSensorManager;
+    private final boolean mUsingBrightnessSensor;
+    private final float mMaxRange;
+    private List<ProximitySensorListener> mListeners = new ArrayList<>();
+    private String mTag = null;
+    @VisibleForTesting ProximityEvent mLastEvent;
+    private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
+    private boolean mPaused;
+    private boolean mRegistered;
+
+    private SensorEventListener mSensorEventListener = new SensorEventListener() {
+        @Override
+        public synchronized void onSensorChanged(SensorEvent event) {
+            onSensorEvent(event);
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+
+    @Inject
+    public ProximitySensor(Context context, AsyncSensorManager sensorManager) {
+        mSensorManager = sensorManager;
+        Sensor sensor = findBrightnessSensor(context);
+
+        if (sensor == null) {
+            mUsingBrightnessSensor = false;
+            sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        } else {
+            mUsingBrightnessSensor = true;
+        }
+        mSensor = sensor;
+        if (mSensor != null) {
+            mMaxRange = mSensor.getMaximumRange();
+        } else {
+            mMaxRange = 0;
+        }
+    }
+
+    public void setTag(String tag) {
+        mTag = tag;
+    }
+
+    public void setSensorDelay(int sensorDelay) {
+        mSensorDelay = sensorDelay;
+    }
+
+    /**
+     * Unregister with the {@link SensorManager} without unsetting listeners on this object.
+     */
+    public void pause() {
+        mPaused = true;
+        unregisterInternal();
+    }
+
+    /**
+     * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
+     */
+    public void resume() {
+        mPaused = false;
+        registerInternal();
+    }
+
+    private Sensor findBrightnessSensor(Context context) {
+        String sensorType = context.getString(R.string.doze_brightness_sensor_type);
+        List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        Sensor sensor = null;
+        for (Sensor s : sensorList) {
+            if (sensorType.equals(s.getStringType())) {
+                sensor = s;
+                break;
+            }
+        }
+
+        return sensor;
+    }
+
+    /**
+     * Returns true if we are registered with the SensorManager.
+     */
+    public boolean isRegistered() {
+        return mRegistered;
+    }
+
+    /**
+     * Returns {@code false} if a Proximity sensor is not available.
+     */
+    public boolean getSensorAvailable() {
+        return mSensor != null;
+    }
+
+    /**
+     * Add a listener.
+     *
+     * Registers itself with the {@link SensorManager} if this is the first listener
+     * added. If a cool down is currently running, the sensor will be registered when it is over.
+     */
+    public boolean register(ProximitySensorListener listener) {
+        if (!getSensorAvailable()) {
+            return false;
+        }
+
+        mListeners.add(listener);
+        registerInternal();
+
+        return true;
+    }
+
+    protected void registerInternal() {
+        if (mRegistered || mPaused || mListeners.isEmpty()) {
+            return;
+        }
+        logDebug("Using brightness sensor? " + mUsingBrightnessSensor);
+        logDebug("Registering sensor listener");
+        mRegistered = true;
+        mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
+    }
+
+    /**
+     * Remove a listener.
+     *
+     * If all listeners are removed from an instance of this class,
+     * it will unregister itself with the SensorManager.
+     */
+    public void unregister(ProximitySensorListener listener) {
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            unregisterInternal();
+        }
+    }
+
+    protected void unregisterInternal() {
+        if (!mRegistered) {
+            return;
+        }
+        logDebug("unregistering sensor listener");
+        mSensorManager.unregisterListener(mSensorEventListener);
+        mRegistered = false;
+    }
+
+    public Boolean isNear() {
+        return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null;
+    }
+
+    /** Update all listeners with the last value this class received from the sensor. */
+    public void alertListeners() {
+        mListeners.forEach(proximitySensorListener ->
+                proximitySensorListener.onSensorEvent(mLastEvent));
+    }
+
+    private void onSensorEvent(SensorEvent event) {
+        boolean near = event.values[0] < mMaxRange;
+        if (mUsingBrightnessSensor) {
+            near = event.values[0] == 0;
+        }
+        mLastEvent = new ProximityEvent(near, event.timestamp);
+        alertListeners();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}",
+                isRegistered(), mPaused, isNear(), mSensor);
+    }
+
+    /**
+     * Convenience class allowing for briefly checking the proximity sensor.
+     */
+    public static class ProximityCheck implements Runnable {
+
+        private final ProximitySensor mSensor;
+        private final Handler mHandler;
+        private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
+
+        @Inject
+        public ProximityCheck(ProximitySensor sensor, Handler handler) {
+            mSensor = sensor;
+            mSensor.setTag("prox_check");
+            mHandler = handler;
+            mSensor.pause();
+            ProximitySensorListener listener = proximityEvent -> {
+                mCallbacks.forEach(
+                        booleanConsumer ->
+                                booleanConsumer.accept(
+                                        proximityEvent == null ? null : proximityEvent.getNear()));
+                mCallbacks.clear();
+                mSensor.pause();
+            };
+            mSensor.register(listener);
+        }
+
+        /** Set a descriptive tag for the sensors registration. */
+        public void setTag(String tag) {
+            mSensor.setTag(tag);
+        }
+
+        @Override
+        public void run() {
+            mSensor.pause();
+            mSensor.alertListeners();
+        }
+
+        /**
+         * Query the proximity sensor, timing out if no result.
+         */
+        public void check(long timeoutMs, Consumer<Boolean> callback) {
+            if (!mSensor.getSensorAvailable()) {
+                callback.accept(null);
+            }
+            mCallbacks.add(callback);
+            if (!mSensor.isRegistered()) {
+                mSensor.resume();
+                mHandler.postDelayed(this, timeoutMs);
+            }
+        }
+    }
+
+    /** Implement to be notified of ProximityEvents. */
+    public interface ProximitySensorListener {
+        /** Called when the ProximitySensor changes. */
+        void onSensorEvent(ProximityEvent proximityEvent);
+    }
+
+    /**
+     * Returned when the near/far state of a {@link ProximitySensor} changes.
+     */
+    public static class ProximityEvent {
+        private final boolean mNear;
+        private final long mTimestampNs;
+
+        public ProximityEvent(boolean near, long timestampNs) {
+            mNear = near;
+            mTimestampNs = timestampNs;
+        }
+
+        public boolean getNear() {
+            return mNear;
+        }
+
+        public long getTimestampNs() {
+            return mTimestampNs;
+        }
+
+        public long getTimestampMs() {
+            return mTimestampNs / 1000000;
+        }
+
+        @Override
+        public String toString() {
+            return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs);
+        }
+
+    }
+
+    private void logDebug(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5412cde..81e2c22 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -39,7 +39,7 @@
     telephony-common \
     android.test.base \
 
-LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui:com.android.keyguard
+LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui
 
 # sign this with platform cert, so this test is allowed to inject key events into
 # UI it doesn't own. This is necessary to allow screenshots to be taken
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 1ae1b97..2e94c7c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -22,11 +22,14 @@
 import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
 
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -34,18 +37,22 @@
 
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.provider.Settings;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.systemui.Dependency;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 
@@ -75,6 +82,9 @@
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
             TEST_CARRIER_ID, 0);
+    private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0,
+            TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
+            null, null, null, null, false, null, "");
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
             TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
@@ -111,6 +121,7 @@
         mDependency.injectMockDependency(WakefulnessLifecycle.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 new Handler(mTestableLooper.getLooper()));
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
 
         mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
                 new CharSequence[]{}, false, new int[]{});
@@ -279,6 +290,65 @@
     }
 
     @Test
+    public void testCarrierText_noTextOnReadySimWhenNull() {
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
+                TextUtils.isEmpty(captor.getValue().carrierText));
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+    }
+
+    @Test
+    public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn() {
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+        reset(mCarrierTextCallback);
+        List<SubscriptionInfo> list = new ArrayList<>();
+        list.add(TEST_SUBSCRIPTION_NULL);
+        when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(IccCardConstants.State.READY);
+        when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(list);
+        mockWifi();
+
+        mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+        ServiceState ss = mock(ServiceState.class);
+        when(ss.getDataRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
+
+        ArgumentCaptor<CarrierTextController.CarrierTextCallbackInfo> captor =
+                ArgumentCaptor.forClass(
+                        CarrierTextController.CarrierTextCallbackInfo.class);
+
+        mCarrierTextController.updateCarrierText();
+        mTestableLooper.processAllMessages();
+        verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+        assertFalse("No SIM should be available", captor.getValue().anySimReady);
+        // There's no airplane mode if at least one SIM is State.READY and there's wifi
+        assertFalse("Device should not be in airplane mode", captor.getValue().airplaneMode);
+        assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+    }
+
+    private void mockWifi() {
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        WifiInfo wifiInfo = mock(WifiInfo.class);
+        when(wifiInfo.getBSSID()).thenReturn("");
+        when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
+    }
+
+    @Test
     public void testCreateInfo_noSubscriptions() {
         reset(mCarrierTextCallback);
         when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(anyBoolean())).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
index dcafa72..00f88bf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
@@ -27,6 +27,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index f01c0b4..de7664c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -39,6 +39,7 @@
 import android.widget.TextClock;
 
 import com.android.keyguard.clock.ClockManager;
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ClockPlugin;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
index a69fd56..25f279b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
@@ -39,6 +39,7 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mKeyguardHostView = new KeyguardHostView(getContext());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
index 5f03bdb..b4363cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewTest.kt
@@ -21,6 +21,7 @@
 import android.testing.TestableLooper
 import android.view.LayoutInflater
 
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.google.common.truth.Truth.assertThat
@@ -41,6 +42,7 @@
     @Before
     fun setup() {
         val inflater = LayoutInflater.from(context)
+        mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
         mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null)
                 as KeyguardPatternView
         mSecurityMessage = KeyguardMessageArea(mContext, null,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
index eadb1b6..6666a92 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java
@@ -28,6 +28,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Before;
@@ -52,6 +53,7 @@
     @Before
     public void setup() {
         LayoutInflater inflater = LayoutInflater.from(getContext());
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mKeyguardPinView =
                 (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null);
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 8138420..082782d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -36,6 +36,7 @@
 public class KeyguardPresentationTest extends SysuiTestCase {
     @Test
     public void testInflation_doesntCrash() {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 8db195a..d47fcee 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index b3accbc..116f8fc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -27,6 +27,7 @@
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
 
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index 31ea39c..e4b83cc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
+import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.util.Assert;
@@ -50,6 +51,7 @@
     @Before
     public void setUp() {
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
         LayoutInflater layoutInflater = inflationController
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index fce79c3..ad7bba1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -51,7 +51,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
@@ -72,12 +71,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-// We must run on the main looper because KeyguardUpdateMonitor#mHandler is initialized with
-// new Handler(Looper.getMainLooper()).
-//
-// Using the main looper should be avoided whenever possible, please don't copy this over to
-// new tests.
-@RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String TEST_CARRIER = "TEST_CARRIER";
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
@@ -501,7 +495,7 @@
         AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
 
         protected TestableKeyguardUpdateMonitor(Context context) {
-            super(context);
+            super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper());
             context.unregisterReceiver(mBroadcastReceiver);
             context.unregisterReceiver(mBroadcastAllReceiver);
             mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index b1ca169..939df10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -74,6 +74,9 @@
     @Test
     public void testInitDependency() {
         Dependency.clearDependencies();
-        Dependency.initDependencies(mContext);
+        Dependency dependency = new Dependency();
+        SystemUIFactory
+                .getInstance().getRootComponent().createDependency().createSystemUI(dependency);
+        dependency.start();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index e07d17f..a07f25a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -29,6 +29,7 @@
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.util.Assert;
@@ -48,6 +49,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         Context context = getContext();
         mRow = new NotificationTestHelper(context).createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 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/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index e4f33e2..819a7f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -30,6 +30,8 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.util.Assert;
 
 import org.junit.After;
@@ -74,7 +76,10 @@
                     "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
         });
         InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
-        KeyguardUpdateMonitor.disableHandlerCheckForTesting(inst);
+        // A lot of tests get the FalsingManager, often via several layers of indirection.
+        // None of them actually need it.
+        mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 18bf75e..0c53b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -25,10 +25,6 @@
     private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>();
 
     public TestableDependency(Context context) {
-        if (context instanceof SysuiTestableContext) {
-            mComponents = ((SysuiTestableContext) context).getComponents();
-        }
-        mContext = context;
         SystemUIFactory.createFromConfig(context);
         SystemUIFactory.getInstance().getRootComponent()
                 .createDependency()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
index a583b1c..9c920f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java
@@ -37,10 +37,12 @@
 
 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;
@@ -50,6 +52,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.EnumMap;
+import java.util.Map;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -63,24 +68,40 @@
     @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;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
+        mDependency.injectMockDependency(DumpController.class);
         doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class));
         doAnswer(answerVoid(Runnable::run)).when(mMockHandler)
                 .postDelayed(any(Runnable.class), anyLong());
 
+        Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController> behaviorMap =
+                new EnumMap<>(AssistHandleBehavior.class);
+        behaviorMap.put(AssistHandleBehavior.OFF, mMockOffBehavior);
+        behaviorMap.put(AssistHandleBehavior.LIKE_HOME, mMockLikeHomeBehavior);
+        behaviorMap.put(AssistHandleBehavior.REMINDER_EXP, mMockReminderExpBehavior);
+        behaviorMap.put(AssistHandleBehavior.TEST, mMockTestBehavior);
+
         mAssistHandleBehaviorController =
                 new AssistHandleBehaviorController(
                         mContext,
                         mMockAssistUtils,
-                        mMockHandler, () -> mMockScreenDecorations,
+                        mMockHandler,
+                        () -> mMockScreenDecorations,
                         mMockPhenotypeHelper,
-                        mMockBehaviorController);
+                        behaviorMap,
+                        mMockNavigationModeController,
+                        mMockDumpController);
     }
 
     @After
@@ -314,8 +335,8 @@
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
 
         // Assert
-        verify(mMockBehaviorController).onModeActivated(mContext, mAssistHandleBehaviorController);
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verify(mMockTestBehavior).onModeActivated(mContext, mAssistHandleBehaviorController);
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 
     @Test
@@ -324,14 +345,14 @@
         when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME);
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.TEST);
         mAssistHandleBehaviorController.setInGesturalModeForTest(true);
-        reset(mMockBehaviorController);
+        reset(mMockTestBehavior);
 
         // Act
         mAssistHandleBehaviorController.setBehavior(AssistHandleBehavior.OFF);
 
         // Assert
-        verify(mMockBehaviorController).onModeDeactivated();
-        verifyNoMoreInteractions(mMockBehaviorController);
+        verify(mMockTestBehavior).onModeDeactivated();
+        verifyNoMoreInteractions(mMockTestBehavior);
     }
 
     @Test
@@ -344,6 +365,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/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
new file mode 100644
index 0000000..b907cdb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import com.android.systemui.R;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricFaceViewTest extends SysuiTestCase {
+
+    @Mock
+    AuthBiometricView.Callback mCallback;
+
+    private TestableFaceView mFaceView;
+
+    @Mock private Button mNegativeButton;
+    @Mock private Button mPositiveButton;
+    @Mock private Button mTryAgainButton;
+    @Mock private TextView mErrorView;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mFaceView = new TestableFaceView(mContext);
+        mFaceView.mIconController = mock(TestableFaceView.TestableIconController.class);
+        mFaceView.setCallback(mCallback);
+        mFaceView.mNegativeButton = mNegativeButton;
+        mFaceView.mPositiveButton = mPositiveButton;
+        mFaceView.mTryAgainButton = mTryAgainButton;
+        mFaceView.mIndicatorView = mErrorView;
+    }
+
+    @Test
+    public void testStateUpdated_whenDialogAnimatedIn() {
+        mFaceView.onDialogAnimatedIn();
+        verify(mFaceView.mIconController)
+                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+    }
+
+    @Test
+    public void testIconUpdatesState_whenDialogStateUpdated() {
+        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
+        verify(mFaceView.mIconController)
+                .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+
+        mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
+        verify(mFaceView.mIconController).updateState(
+                eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
+                eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
+    }
+
+    public class TestableFaceView extends AuthBiometricFaceView {
+
+        public class TestableIconController extends IconController {
+            TestableIconController(Context context, ImageView iconView) {
+                super(context, iconView, mock(TextView.class));
+            }
+
+            public void startPulsing() {
+                // Stub for testing
+            }
+        }
+
+        @Override
+        protected int getDelayAfterAuthenticatedDurationMs() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+
+        public TestableFaceView(Context context) {
+            super(context);
+        }
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
new file mode 100644
index 0000000..7a09137
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricViewTest extends SysuiTestCase {
+
+    @Mock private AuthBiometricView.Callback mCallback;
+    @Mock private AuthPanelController mPanelController;
+
+    @Mock private Button mNegativeButton;
+    @Mock private Button mPositiveButton;
+    @Mock private Button mTryAgainButton;
+    @Mock private TextView mTitleView;
+    @Mock private TextView mSubtitleView;
+    @Mock private TextView mDescriptionView;
+    @Mock private TextView mIndicatorView;
+    @Mock private ImageView mIconView;
+
+    private TestableBiometricView mBiometricView;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        // The onAuthenticated runnable is posted when authentication succeeds.
+        mBiometricView.onAuthenticationSucceeded();
+        waitForIdleSync();
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+    }
+
+    @Test
+    public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        mBiometricView.setRequireConfirmation(true);
+        mBiometricView.onAuthenticationSucceeded();
+        waitForIdleSync();
+        assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
+        verify(mCallback, never()).onAction(anyInt());
+        verify(mBiometricView.mNegativeButton).setText(eq(R.string.cancel));
+        verify(mBiometricView.mPositiveButton).setEnabled(eq(true));
+        verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm));
+        verify(mIndicatorView).setVisibility(eq(View.VISIBLE));
+    }
+
+    @Test
+    public void testPositiveButton_sendsActionAuthenticated() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+           @Override
+            public Button getPositiveButton() {
+               return button;
+           }
+        });
+
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+    }
+
+    @Test
+    public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getNegativeButton() {
+                return button;
+            }
+        });
+
+        mBiometricView.onDialogAnimatedIn();
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+    }
+
+    @Test
+    public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getNegativeButton() {
+                return button;
+            }
+        });
+
+        mBiometricView.setRequireConfirmation(true);
+        mBiometricView.onAuthenticationSucceeded();
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
+    }
+
+    @Test
+    public void testTryAgainButton_sendsActionTryAgain() {
+        Button button = new Button(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return button;
+            }
+        });
+
+        button.performClick();
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+        assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
+    }
+
+    @Test
+    public void testError_sendsActionError() {
+        initDialog(mContext, mCallback, new MockInjector());
+        final String testError = "testError";
+        mBiometricView.onError(testError);
+        waitForIdleSync();
+
+        verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_ERROR);
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+    }
+
+    @Test
+    public void testBackgroundClicked_sendsActionUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        view.performClick();
+        verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        mBiometricView.onAuthenticationSucceeded();
+        view.performClick();
+        verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
+        initDialog(mContext, mCallback, new MockInjector());
+        mBiometricView.setPanelController(mPanelController);
+        mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
+
+        View view = new View(mContext);
+        mBiometricView.setBackgroundView(view);
+        view.performClick();
+        verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+    }
+
+    @Test
+    public void testRestoresState() {
+        final boolean requireConfirmation = true; // set/init from AuthController
+
+        Button tryAgainButton = new Button(mContext);
+        TextView indicatorView = new TextView(mContext);
+        initDialog(mContext, mCallback, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return tryAgainButton;
+            }
+            @Override
+            public TextView getIndicatorView() {
+                return indicatorView;
+            }
+        });
+
+        final String failureMessage = "testFailureMessage";
+        mBiometricView.setRequireConfirmation(requireConfirmation);
+        mBiometricView.onAuthenticationFailed(failureMessage);
+        waitForIdleSync();
+
+        Bundle state = new Bundle();
+        mBiometricView.onSaveState(state);
+
+        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+        assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
+
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+        assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
+
+        assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+        assertTrue(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING));
+
+        assertEquals(failureMessage, mBiometricView.mIndicatorView.getText());
+        assertEquals(failureMessage, state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING));
+
+        // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
+
+        // Create new dialog and restore the previous state into it
+        Button tryAgainButton2 = new Button(mContext);
+        TextView indicatorView2 = new TextView(mContext);
+        initDialog(mContext, mCallback, state, new MockInjector() {
+            @Override
+            public Button getTryAgainButton() {
+                return tryAgainButton2;
+            }
+            @Override
+            public TextView getIndicatorView() {
+                return indicatorView2;
+            }
+        });
+        mBiometricView.setRequireConfirmation(requireConfirmation);
+        waitForIdleSync();
+
+        // Test restored state
+        assertEquals(View.VISIBLE, tryAgainButton.getVisibility());
+        assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState);
+        assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility());
+
+        // TODO: Test restored text. Currently cannot test this, since it gets restored only after
+        // dialog size is known.
+    }
+
+    private Bundle buildBiometricPromptBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, "Title");
+        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, "Negative");
+        return bundle;
+    }
+
+    private void initDialog(Context context, AuthBiometricView.Callback callback,
+            Bundle savedState, MockInjector injector) {
+        mBiometricView = new TestableBiometricView(context, null, injector);
+        mBiometricView.setBiometricPromptBundle(buildBiometricPromptBundle());
+        mBiometricView.setCallback(callback);
+        mBiometricView.restoreState(savedState);
+        mBiometricView.onFinishInflateInternal();
+        mBiometricView.onAttachedToWindowInternal();
+    }
+
+    private void initDialog(Context context, AuthBiometricView.Callback callback,
+            MockInjector injector) {
+        initDialog(context, callback, null /* savedState */, injector);
+    }
+
+    private class MockInjector extends AuthBiometricView.Injector {
+        @Override
+        public Button getNegativeButton() {
+            return mNegativeButton;
+        }
+
+        @Override
+        public Button getPositiveButton() {
+            return mPositiveButton;
+        }
+
+        @Override
+        public Button getTryAgainButton() {
+            return mTryAgainButton;
+        }
+
+        @Override
+        public TextView getTitleView() {
+            return mTitleView;
+        }
+
+        @Override
+        public TextView getSubtitleView() {
+            return mSubtitleView;
+        }
+
+        @Override
+        public TextView getDescriptionView() {
+            return mDescriptionView;
+        }
+
+        @Override
+        public TextView getIndicatorView() {
+            return mIndicatorView;
+        }
+
+        @Override
+        public ImageView getIconView() {
+            return mIconView;
+        }
+
+        @Override
+        public int getDelayAfterError() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+    }
+
+    private class TestableBiometricView extends AuthBiometricView {
+        TestableBiometricView(Context context, AttributeSet attrs,
+                Injector injector) {
+            super(context, attrs, injector);
+        }
+
+        @Override
+        protected int getDelayAfterAuthenticatedDurationMs() {
+            return 0; // Keep this at 0 for tests to invoke callback immediately.
+        }
+
+        @Override
+        protected int getStateForAfterError() {
+            return 0;
+        }
+
+        @Override
+        protected void handleResetAfterError() {
+
+        }
+
+        @Override
+        protected void handleResetAfterHelp() {
+
+        }
+
+        @Override
+        protected boolean supportsSmallDialog() {
+            return false;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
new file mode 100644
index 0000000..d4fc3f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthContainerViewTest extends SysuiTestCase {
+
+    private TestableAuthContainer mAuthContainer;
+
+    private @Mock AuthDialogCallback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        AuthContainerView.Config config = new AuthContainerView.Config();
+        config.mContext = mContext;
+        config.mCallback = mCallback;
+        mAuthContainer = new TestableAuthContainer(config);
+    }
+
+    @Test
+    public void testActionAuthenticated_sendsDismissedAuthenticated() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_AUTHENTICATED));
+    }
+
+    @Test
+    public void testActionUserCanceled_sendsDismissedUserCanceled() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_USER_CANCELED);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED));
+    }
+
+    @Test
+    public void testActionButtonNegative_sendsDismissedButtonNegative() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+        verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE));
+    }
+
+    @Test
+    public void testActionTryAgain_sendsTryAgain() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+        verify(mCallback).onTryAgainPressed();
+    }
+
+    @Test
+    public void testActionError_sendsDismissedError() {
+        mAuthContainer.mBiometricCallback.onAction(
+                AuthBiometricView.Callback.ACTION_ERROR);
+        verify(mCallback).onDismissed(AuthDialogCallback.DISMISSED_ERROR);
+    }
+
+    private class TestableAuthContainer extends AuthContainerView {
+        TestableAuthContainer(AuthContainerView.Config config) {
+            super(config);
+        }
+
+        @Override
+        public void animateAway(int reason) {
+            mConfig.mCallback.onDismissed(reason);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 8f2f8b1..eb7be4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -58,16 +58,16 @@
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
 @SmallTest
-public class BiometricDialogImplTest extends SysuiTestCase {
+public class AuthControllerTest extends SysuiTestCase {
 
     @Mock
     private PackageManager mPackageManager;
     @Mock
     private IBiometricServiceReceiverInternal mReceiver;
     @Mock
-    private BiometricDialog mDialog1;
+    private AuthDialog mDialog1;
     @Mock
-    private BiometricDialog mDialog2;
+    private AuthDialog mDialog2;
 
     private TestableBiometricDialogImpl mBiometricDialogImpl;
 
@@ -102,42 +102,42 @@
     @Test
     public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_USER_CANCELED);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
     }
 
     @Test
     public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
     }
 
     @Test
     public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
     }
 
     @Test
     public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_AUTHENTICATED);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_AUTHENTICATED);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
     }
 
     @Test
     public void testSendsReasonError_whenDismissedByError() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_ERROR);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
     }
 
     @Test
     public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
+        mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
         verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
     }
 
@@ -147,7 +147,7 @@
     public void testShowInvoked_whenSystemRequested()
             throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+        verify(mDialog1).show(any(), any());
     }
 
     @Test
@@ -215,7 +215,7 @@
     @Test
     public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+        verify(mDialog1).show(any(), any());
 
         showDialog(BiometricPrompt.TYPE_FACE);
 
@@ -223,13 +223,13 @@
         verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
 
         // Second dialog should be shown without animation
-        verify(mDialog2).show(any(), eq(true)) /* skipIntro */;
+        verify(mDialog2).show(any(), any());
     }
 
     @Test
     public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
         showDialog(BiometricPrompt.TYPE_FACE);
-        verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+        verify(mDialog1).show(any(), any());
 
         mBiometricDialogImpl.onConfigurationChanged(new Configuration());
 
@@ -241,10 +241,7 @@
 
         // Saved state is restored into new dialog
         ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog2).restoreState(captor2.capture());
-
-        // Dialog for new configuration skips intro
-        verify(mDialog2).show(any(), eq(true) /* skipIntro */);
+        verify(mDialog2).show(any(), captor2.capture());
 
         // TODO: This should check all values we want to save/restore
         assertEquals(captor.getValue(), captor2.getValue());
@@ -296,7 +293,7 @@
         return bundle;
     }
 
-    private final class TestableBiometricDialogImpl extends BiometricDialogImpl {
+    private final class TestableBiometricDialogImpl extends AuthController {
         private int mBuildCount = 0;
 
         public TestableBiometricDialogImpl(Injector injector) {
@@ -304,9 +301,10 @@
         }
 
         @Override
-        protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
-                boolean requireConfirmation, int userId, int type, String opPackageName) {
-            BiometricDialog dialog;
+        protected AuthDialog buildDialog(Bundle biometricPromptBundle,
+                boolean requireConfirmation, int userId, int type, String opPackageName,
+                boolean skipIntro) {
+            AuthDialog dialog;
             if (mBuildCount == 0) {
                 dialog = mDialog1;
             } else if (mBuildCount == 1) {
@@ -319,7 +317,7 @@
         }
     }
 
-    private final class MockInjector extends BiometricDialogImpl.Injector {
+    private final class MockInjector extends AuthController.Injector {
         @Override
         IActivityTaskManager getActivityTaskManager() {
             return mock(IActivityTaskManager.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
deleted file mode 100644
index bbdd837..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics.ui;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.spy;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.hardware.biometrics.BiometricPrompt;
-import android.os.Bundle;
-import android.os.UserManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.DialogViewCallback;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class BiometricDialogViewTest extends SysuiTestCase {
-
-    FaceDialogView mFaceDialogView;
-
-    private static final String TITLE = "Title";
-    private static final String SUBTITLE = "Subtitle";
-    private static final String DESCRIPTION = "Description";
-    private static final String NEGATIVE_BUTTON = "Negative Button";
-
-    private static final String TEST_HELP = "Help";
-
-    TestableContext mTestableContext;
-    @Mock
-    private DialogViewCallback mCallback;
-    @Mock
-    private UserManager mUserManager;
-    @Mock
-    private DevicePolicyManager mDpm;
-
-    private static class Injector extends BiometricDialogView.Injector {
-        @Override
-        public WakefulnessLifecycle getWakefulnessLifecycle() {
-            final WakefulnessLifecycle lifecycle = new WakefulnessLifecycle();
-            lifecycle.dispatchFinishedWakingUp();
-            return lifecycle;
-        }
-    }
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        mTestableContext = spy(mContext);
-        mTestableContext.addMockSystemService(UserManager.class, mUserManager);
-        mTestableContext.addMockSystemService(DevicePolicyManager.class, mDpm);
-    }
-
-    @Test
-    public void testContentStates_confirmationRequired_authenticated() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                true /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-
-        // When starting authentication
-        assertEquals(View.VISIBLE, mFaceDialogView.mTitleText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
-        assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(View.VISIBLE, mFaceDialogView.mNegativeButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-
-        // Contents are as expected
-        assertTrue(TITLE.contentEquals(mFaceDialogView.mTitleText.getText()));
-        assertTrue(SUBTITLE.contentEquals(mFaceDialogView.mSubtitleText.getText()));
-        assertTrue(DESCRIPTION.contentEquals(mFaceDialogView.mDescriptionText.getText()));
-        assertTrue(mFaceDialogView.mPositiveButton.getText().toString()
-                .contentEquals(mContext.getString(R.string.biometric_dialog_confirm)));
-        assertTrue(NEGATIVE_BUTTON.contentEquals(mFaceDialogView.mNegativeButton.getText()));
-        assertTrue(mFaceDialogView.mTryAgainButton.getText().toString()
-                .contentEquals(mContext.getString(R.string.biometric_dialog_try_again)));
-
-        // When help message is received
-        mFaceDialogView.onHelp(TEST_HELP);
-        assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
-        assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
-
-        // When authenticated, confirm button comes out
-        mFaceDialogView.onAuthenticationSucceeded();
-        assertEquals(View.VISIBLE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(true, mFaceDialogView.mPositiveButton.isEnabled());
-    }
-
-    @Test
-    public void testContentStates_confirmationNotRequired_authenticated() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-        mFaceDialogView.updateSize(FaceDialogView.SIZE_SMALL);
-
-        assertEquals(View.INVISIBLE, mFaceDialogView.mTitleText.getVisibility());
-        assertNotSame(View.VISIBLE, mFaceDialogView.mSubtitleText.getVisibility());
-        assertNotSame(View.VISIBLE, mFaceDialogView.mDescriptionText.getVisibility());
-        assertEquals(View.INVISIBLE, mFaceDialogView.mErrorText.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mPositiveButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-        assertEquals(View.GONE, mFaceDialogView.mTryAgainButton.getVisibility());
-    }
-
-    @Test
-    public void testContentStates_confirmationNotRequired_help() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-
-        mFaceDialogView.onHelp(TEST_HELP);
-        assertEquals(mFaceDialogView.mErrorText.getVisibility(), View.VISIBLE);
-        assertTrue(TEST_HELP.contentEquals(mFaceDialogView.mErrorText.getText()));
-    }
-
-    @Test
-    public void testBack_sendsUserCanceled() {
-        // TODO: Need robolectric framework to wait for handler to complete
-    }
-
-    @Test
-    public void testScreenOff_sendsUserCanceled() {
-        // TODO: Need robolectric framework to wait for handler to complete
-    }
-
-    @Test
-    public void testRestoreState_contentStatesCorrect() {
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.onAttachedToWindow();
-        mFaceDialogView.onAuthenticationFailed(TEST_HELP);
-
-        final Bundle bundle = new Bundle();
-        mFaceDialogView.onSaveState(bundle);
-
-        mFaceDialogView = buildFaceDialogView(mTestableContext, mCallback,
-                false /* requireConfirmation */);
-        mFaceDialogView.restoreState(bundle);
-        mFaceDialogView.onAttachedToWindow();
-
-        assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility());
-    }
-
-    private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback,
-            boolean requireConfirmation) {
-        return (FaceDialogView) new BiometricDialogView.Builder(context)
-                .setCallback(callback)
-                .setBiometricPromptBundle(createTestDialogBundle())
-                .setRequireConfirmation(requireConfirmation)
-                .setUserId(0)
-                .setOpPackageName("test_package")
-                .build(BiometricDialogView.Builder.TYPE_FACE, new Injector());
-    }
-
-    private Bundle createTestDialogBundle() {
-        Bundle bundle = new Bundle();
-
-        bundle.putCharSequence(BiometricPrompt.KEY_TITLE, TITLE);
-        bundle.putCharSequence(BiometricPrompt.KEY_SUBTITLE, SUBTITLE);
-        bundle.putCharSequence(BiometricPrompt.KEY_DESCRIPTION, DESCRIPTION);
-        bundle.putCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT, NEGATIVE_BUTTON);
-
-        // RequireConfirmation is a hint to BiometricService. This can be forced to be required
-        // by user settings, and should be tested in BiometricService.
-        bundle.putBoolean(BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true);
-
-        return bundle;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2221915..448c80e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.bubbles;
 
 import static android.app.Notification.FLAG_BUBBLE;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
@@ -28,6 +27,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,11 +43,10 @@
 import android.app.IActivityManager;
 import android.app.Notification;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.graphics.drawable.Icon;
+import android.hardware.face.FaceManager;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -57,18 +57,22 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationData;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -82,20 +86,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class BubbleControllerTest extends SysuiTestCase {
 
-    // Some APIs rely on the app being foreground, check is via pkg name
-    private static final String FOREGROUND_TEST_PKG_NAME = "com.android.systemui.tests";
-
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
+    private NotificationGroupManager mNotificationGroupManager;
+    @Mock
     private WindowManager mWindowManager;
     @Mock
     private IActivityManager mActivityManager;
@@ -108,6 +108,10 @@
     @Mock
     private ZenModeConfig mZenModeConfig;
     @Mock
+    private FaceManager mFaceManager;
+    @Mock
+    private NotificationLockscreenUserManager mLockscreenUserManager;
+    @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
@@ -126,8 +130,6 @@
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
-    private ExpandableNotificationRow mAutoExpandRow;
-    private ExpandableNotificationRow mSuppressNotifRow;
     private ExpandableNotificationRow mNonBubbleNotifRow;
 
     @Mock
@@ -146,6 +148,7 @@
         MockitoAnnotations.initMocks(this);
         mStatusBarView = new FrameLayout(mContext);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
+        mContext.addMockSystemService(FaceManager.class, mFaceManager);
 
         // Bubbles get added to status bar window view
         mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
@@ -159,35 +162,32 @@
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
         mNonBubbleNotifRow = mNotificationTestHelper.createRow();
 
-        // Some bubbles want to auto expand
-        Notification.BubbleMetadata autoExpandMetadata =
-                getBuilder().setAutoExpandBubble(true).build();
-        mAutoExpandRow = mNotificationTestHelper.createBubble(autoExpandMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
-        // Some bubbles want to suppress notifs
-        Notification.BubbleMetadata suppressNotifMetadata =
-                getBuilder().setSuppressNotification(true).build();
-        mSuppressNotifRow = mNotificationTestHelper.createBubble(suppressNotifMetadata,
-                FOREGROUND_TEST_PKG_NAME);
-
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
-        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
+        when(mNotificationData.get(mRow.getEntry().key)).thenReturn(mRow.getEntry());
+        when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(
+                mRow.getEntry().getChannel());
 
         mZenModeConfig.suppressedVisualEffects = 0;
         when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
 
         TestableNotificationInterruptionStateProvider interruptionStateProvider =
-                new TestableNotificationInterruptionStateProvider(mContext);
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mock(NotificationFilter.class),
+                        mock(StatusBarStateController.class));
         interruptionStateProvider.setUpWithPresenter(
                 mock(NotificationPresenter.class),
                 mock(HeadsUpManager.class),
                 mock(NotificationInterruptionStateProvider.HeadsUpSuppressor.class));
         mBubbleData = new BubbleData(mContext);
-        mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
-                mBubbleData, mConfigurationController, interruptionStateProvider,
-                mZenModeController);
+        mBubbleController = new TestableBubbleController(mContext,
+                mStatusBarWindowController,
+                mBubbleData,
+                mConfigurationController,
+                interruptionStateProvider,
+                mZenModeController,
+                mLockscreenUserManager,
+                mNotificationGroupManager);
         mBubbleController.setBubbleStateChangeListener(mBubbleStateChangeListener);
         mBubbleController.setExpandListener(mBubbleExpandListener);
 
@@ -219,13 +219,14 @@
     @Test
     public void testRemoveBubble() {
         mBubbleController.updateBubble(mRow.getEntry());
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
         verify(mNotificationEntryManager).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
 
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
-        assertTrue(mRow.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         verify(mNotificationEntryManager, times(2)).updateNotifications();
         verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
     }
@@ -236,10 +237,10 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Make it look like dismissed notif
-        mRow.getEntry().setShowInShadeWhenBubble(false);
+        mBubbleData.getBubbleWithKey(mRow.getEntry().key).setShowInShadeWhenBubble(false);
 
         // Now remove the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -255,15 +256,17 @@
     public void testDismissStack() {
         mBubbleController.updateBubble(mRow.getEntry());
         verify(mNotificationEntryManager, times(1)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
         mBubbleController.updateBubble(mRow2.getEntry());
         verify(mNotificationEntryManager, times(2)).updateNotifications();
+        assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
         assertTrue(mBubbleController.hasBubbles());
 
         mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mStatusBarWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications();
-        assertTrue(mRow.getEntry().isBubbleDismissed());
-        assertTrue(mRow2.getEntry().isBubbleDismissed());
+        assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
+        assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
     }
 
     @Test
@@ -274,9 +277,9 @@
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         assertFalse(mStatusBarWindowController.getBubbleExpanded());
 
         // Expand the stack
@@ -286,8 +289,8 @@
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
         assertTrue(mStatusBarWindowController.getBubbleExpanded());
 
-        // Make sure it's no longer in the shade
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Make sure the notif is suppressed
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Collapse
         mBubbleController.collapseStack();
@@ -304,10 +307,11 @@
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
-        assertTrue(mRow2.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
+                mRow2.getEntry().key));
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -317,13 +321,13 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Switch which bubble is expanded
         mBubbleController.selectBubble(mRow.getEntry().key);
-        stackView.setExpandedBubble(mRow.getEntry());
+        stackView.setExpandedBubble(mRow.getEntry().key);
         assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // collapse for previous bubble
         verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
@@ -336,14 +340,37 @@
     }
 
     @Test
-    public void testExpansionRemovesShowInShade() {
+    public void testExpansionRemovesShowInShadeAndDot() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry());
 
-        // We should have bubbles & their notifs should show in the shade
+        // We should have bubbles & their notifs should not be suppressed
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Expand
+        mBubbleController.expandStack();
+        assertTrue(mBubbleController.isStackExpanded());
+        verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
+
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+    }
+
+    @Test
+    public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
+        // Mark it as a bubble and add it explicitly
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
+
+        // We should have bubbles & their notifs should not be suppressed
+        assertTrue(mBubbleController.hasBubbles());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
@@ -351,8 +378,19 @@
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
 
-        // No longer show shade in notif after expansion
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
+
+        // Send update
+        mEntryListener.onPreEntryUpdated(mRow.getEntry());
+
+        // Nothing should have changed
+        // Notif is suppressed after expansion
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        // Notif shouldn't show dot after expansion
+        assertFalse(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -373,7 +411,7 @@
 
         // Last added is the one that is expanded
         assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
-        assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry().key));
 
         // Dismiss currently expanded
         mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
@@ -397,14 +435,16 @@
     @Test
     public void testAutoExpand_FailsNotForeground() {
         assertFalse(mBubbleController.isStackExpanded());
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion shouldn't change
         verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertFalse(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
@@ -413,91 +453,51 @@
 
     @Test
     public void testAutoExpand_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
 
         // Add the auto expand bubble
-        mEntryListener.onPendingEntryAdded(mAutoExpandRow.getEntry());
-        mBubbleController.updateBubble(mAutoExpandRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
         // Expansion should change
         verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
-                mAutoExpandRow.getEntry().key);
+                mRow.getEntry().key);
         assertTrue(mBubbleController.isStackExpanded());
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
     public void testSuppressNotif_FailsNotForeground() {
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, false /* enableFlag */);
+
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should show in shade because we weren't forground
-        assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
-
+        // Should not be suppressed because we weren't forground
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
     }
 
     @Test
     public void testSuppressNotif_SucceedsForeground() {
-        final CountDownLatch latch = new CountDownLatch(1);
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        };
-        IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-        mContext.registerReceiver(receiver, filter);
-
-        assertFalse(mBubbleController.isStackExpanded());
-
-        // Make ourselves foreground
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
-        i.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(i);
-
-        try {
-            latch.await(100, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
+        setMetadataFlags(mRow.getEntry(),
+                Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
 
         // Add the suppress notif bubble
-        mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
-        mBubbleController.updateBubble(mSuppressNotifRow.getEntry());
+        mEntryListener.onPendingEntryAdded(mRow.getEntry());
+        mBubbleController.updateBubble(mRow.getEntry());
 
-        // Should NOT show in shade because we were foreground
-        assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
+        // Notif should be suppressed because we were foreground
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // # of bubbles should change
         verify(mBubbleStateChangeListener).onHasBubblesChanged(true /* hasBubbles */);
-        mContext.unregisterReceiver(receiver);
     }
 
     @Test
@@ -516,7 +516,8 @@
     @Test
     public void testMarkNewNotificationAsShowInShade() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
+        assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().key).showBubbleDot());
     }
 
     @Test
@@ -584,7 +585,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL_ALL);
@@ -592,7 +593,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -605,7 +606,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
                 mRow.getEntry().key, REASON_CANCEL);
@@ -613,7 +614,7 @@
         // Intercept!
         assertTrue(intercepted);
         // Should update show in shade state
-        assertFalse(mRow.getEntry().showInShadeWhenBubble());
+        assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         verify(mNotificationEntryManager, never()).performRemoveNotification(
                 any(), anyInt());
@@ -626,7 +627,7 @@
         mBubbleController.updateBubble(mRow.getEntry());
 
         assertTrue(mBubbleController.hasBubbles());
-        assertTrue(mRow.getEntry().showInShadeWhenBubble());
+        assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry().key));
 
         // Dismiss the bubble
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
@@ -646,22 +647,21 @@
                 StatusBarWindowController statusBarWindowController, BubbleData data,
                 ConfigurationController configurationController,
                 NotificationInterruptionStateProvider interruptionStateProvider,
-                ZenModeController zenModeController) {
+                ZenModeController zenModeController,
+                NotificationLockscreenUserManager lockscreenUserManager,
+                NotificationGroupManager groupManager) {
             super(context, statusBarWindowController, data, Runnable::run,
-                    configurationController, interruptionStateProvider, zenModeController);
-        }
-
-        @Override
-        public boolean shouldAutoBubbleForFlags(Context c, NotificationEntry entry) {
-            return entry.notification.getNotification().getBubbleMetadata() != null;
+                    configurationController, interruptionStateProvider, zenModeController,
+                    lockscreenUserManager, groupManager);
         }
     }
 
-    public static class TestableNotificationInterruptionStateProvider extends
+    static class TestableNotificationInterruptionStateProvider extends
             NotificationInterruptionStateProvider {
 
-        public TestableNotificationInterruptionStateProvider(Context context) {
-            super(context);
+        TestableNotificationInterruptionStateProvider(Context context,
+                NotificationFilter filter, StatusBarStateController controller) {
+            super(context, filter, controller);
             mUseHeadsUp = true;
         }
     }
@@ -676,4 +676,21 @@
                 .setIntent(bubbleIntent)
                 .setIcon(Icon.createWithResource(mContext, R.drawable.android));
     }
+
+    /**
+     * Sets the bubble metadata flags for this entry. These flags are normally set by
+     * NotificationManagerService when the notification is sent, however, these tests do not
+     * go through that path so we set them explicitly when testing.
+     */
+    private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
+        Notification.BubbleMetadata bubbleMetadata =
+                entry.notification.getNotification().getBubbleMetadata();
+        int flags = bubbleMetadata.getFlags();
+        if (enableFlag) {
+            flags |= flag;
+        } else {
+            flags &= ~flag;
+        }
+        bubbleMetadata.setFlags(flags);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 3eea853..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) {
@@ -887,7 +888,7 @@
 
     private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
         setPostTime(entry, postTime);
-        mBubbleData.notificationEntryUpdated(entry);
+        mBubbleData.notificationEntryUpdated(entry, /* suppressFlyout=*/ false);
     }
 
     private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
index 173237f..a8961a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
@@ -18,7 +18,6 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.Mockito.verify;
 
@@ -46,6 +45,7 @@
 public class BubbleFlyoutViewTest extends SysuiTestCase {
     private BubbleFlyoutView mFlyout;
     private TextView mFlyoutText;
+    private float[] mDotCenter = new float[2];
 
     @Before
     public void setUp() throws Exception {
@@ -53,20 +53,25 @@
         mFlyout = new BubbleFlyoutView(getContext());
 
         mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+        mDotCenter[0] = 30;
+        mDotCenter[1] = 30;
     }
 
     @Test
     public void testShowFlyout_isVisible() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+        mFlyout.setVisibility(View.VISIBLE);
+
         assertEquals("Hello", mFlyoutText.getText());
         assertEquals(View.VISIBLE, mFlyout.getVisibility());
-        assertEquals(1f, mFlyoutText.getAlpha(), .01f);
     }
 
     @Test
     public void testFlyoutHide_runsCallback() {
         Runnable after = Mockito.mock(Runnable.class);
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, after);
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, after, mDotCenter);
         mFlyout.hideFlyout();
 
         verify(after).run();
@@ -74,19 +79,16 @@
 
     @Test
     public void testSetCollapsePercent() {
-        mFlyout.showFlyout("Hello", new PointF(100, 100), 500, true, Color.WHITE, null);
-
-        float initialTranslationZ = mFlyout.getTranslationZ();
+        mFlyout.setupFlyoutStartingAsDot(
+                "Hello", new PointF(100, 100), 500, true, Color.WHITE, null, null, mDotCenter);
+        mFlyout.setVisibility(View.VISIBLE);
 
         mFlyout.setCollapsePercent(1f);
         assertEquals(0f, mFlyoutText.getAlpha(), 0.01f);
         assertNotSame(0f, mFlyoutText.getTranslationX()); // Should have moved to collapse.
-        assertTrue(mFlyout.getTranslationZ() < initialTranslationZ); // Should be descending.
 
         mFlyout.setCollapsePercent(0f);
         assertEquals(1f, mFlyoutText.getAlpha(), 0.01f);
         assertEquals(0f, mFlyoutText.getTranslationX());
-        assertEquals(initialTranslationZ, mFlyout.getTranslationZ());
-
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
index 57a6aae..5757861 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -14,23 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.bubbles;
 
 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.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;
 import org.junit.Test;
@@ -41,37 +40,25 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class NotificationEntryTest extends SysuiTestCase {
-    @Mock
-    private StatusBarNotification mStatusBarNotification;
+public class BubbleTest extends SysuiTestCase {
     @Mock
     private Notification mNotif;
 
     private NotificationEntry mEntry;
+    private Bubble mBubble;
     private Bundle mExtras;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mStatusBarNotification.getKey()).thenReturn("key");
-        when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
-
         mExtras = new Bundle();
         mNotif.extras = mExtras;
 
-        mEntry = NotificationEntry.buildForTest(mStatusBarNotification);
-    }
-
-    @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());
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotif)
+                .build();
+        mBubble = new Bubble(mContext, mEntry);
     }
 
     @Test
@@ -79,7 +66,7 @@
         final String msg = "Hello there!";
         doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
         mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -90,7 +77,7 @@
         mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
 
         // Should be big text, not the small text.
-        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+        assertEquals(msg, mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -98,7 +85,7 @@
         doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
 
         // Media notifs don't get update messages.
-        assertNull(mEntry.getUpdateMessage(mContext));
+        assertNull(mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -113,7 +100,7 @@
                         "Really? I prefer them that way."});
 
         // Should be the last one only.
-        assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
+        assertEquals("Really? I prefer them that way.", mBubble.getUpdateMessage(mContext));
     }
 
     @Test
@@ -128,6 +115,6 @@
                                 "Oh, hello!", 0, "Mady").toBundle()});
 
         // Should be the last one only.
-        assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
+        assertEquals("Mady: Oh, hello!", mBubble.getUpdateMessage(mContext));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
index 43d2ad1..8bc2e2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.systemui.tests.R;
 
 /**
  * Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index b324235..ae4581a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Mockito.verify;
 
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -45,14 +46,18 @@
 
     private int mDisplayWidth = 500;
     private int mDisplayHeight = 1000;
+    private int mExpandedViewPadding = 10;
+    private int mOrientation = Configuration.ORIENTATION_PORTRAIT;
+    private float mLauncherGridDiff = 30f;
 
     @Spy
     private ExpandedAnimationController mExpandedController =
             new ExpandedAnimationController(
                     new Point(mDisplayWidth, mDisplayHeight) /* displaySize */,
-                    0 /* expandedViewPadding */);
+                    mExpandedViewPadding, mOrientation);
+
     private int mStackOffset;
-    private float mBubblePadding;
+    private float mBubblePaddingTop;
     private float mBubbleSize;
 
     private PointF mExpansionPoint;
@@ -65,12 +70,13 @@
 
         Resources res = mLayout.getResources();
         mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
-        mBubblePadding = res.getDimensionPixelSize(R.dimen.bubble_padding);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
         mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
         mExpansionPoint = new PointF(100, 100);
     }
 
     @Test
+    @Ignore
     public void testExpansionAndCollapse() throws InterruptedException {
         Runnable afterExpand = Mockito.mock(Runnable.class);
         mExpandedController.expandFromStack(afterExpand);
@@ -88,6 +94,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildAdded() throws InterruptedException {
         expand();
 
@@ -100,6 +107,7 @@
     }
 
     @Test
+    @Ignore
     public void testOnChildRemoved() throws InterruptedException {
         expand();
 
@@ -111,6 +119,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDraggedNotDismissedSnapsBack() throws InterruptedException {
         expand();
 
@@ -128,6 +137,7 @@
     }
 
     @Test
+    @Ignore
     public void testBubbleDismissed() throws InterruptedException {
         expand();
 
@@ -138,7 +148,6 @@
         assertEquals(500f, draggedBubble.getTranslationX(), 1f);
         assertEquals(500f, draggedBubble.getTranslationY(), 1f);
 
-        // Snap it back and make sure it made it back correctly.
         mLayout.removeView(draggedBubble);
         waitForLayoutMessageQueue();
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
@@ -174,7 +183,7 @@
 
         waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
 
-        assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+        assertEquals(mBubblePaddingTop, mViews.get(1).getTranslationX(), 1f);
     }
 
     @Test
@@ -256,8 +265,8 @@
      * @return Bubble left x from left edge of screen.
      */
     public float getBubbleLeft(int index) {
-        float bubbleLeftFromRowLeft = index * (mBubbleSize + mBubblePadding);
-        return getRowLeft() + bubbleLeftFromRowLeft;
+        final float bubbleLeft = index * (mBubbleSize + getSpaceBetweenBubbles());
+        return getRowLeft() + bubbleLeft;
     }
 
     private float getRowLeft() {
@@ -265,16 +274,29 @@
             return 0;
         }
         int bubbleCount = mLayout.getChildCount();
+        final float totalBubbleWidth = bubbleCount * mBubbleSize;
+        final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
+        final float rowWidth = totalGapWidth + totalBubbleWidth;
 
-        // Width calculations.
-        double bubble = bubbleCount * mBubbleSize;
-        float gap = (bubbleCount - 1) * mBubblePadding;
-        float row = gap + (float) bubble;
+        final float centerScreen = mDisplayWidth / 2f;
+        final float halfRow = rowWidth / 2f;
+        final float rowLeft = centerScreen - halfRow;
 
-        float halfRow = row / 2f;
-        float centerScreen = mDisplayWidth / 2;
-        float rowLeftFromScreenLeft = centerScreen - halfRow;
+        return rowLeft;
+    }
 
-        return rowLeftFromScreenLeft;
+    /**
+     * @return Space between bubbles in row above expanded view.
+     */
+    private float getSpaceBetweenBubbles() {
+        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float maxRowWidth = mDisplayWidth - rowMargins;
+
+        final float totalBubbleWidth = mMaxBubbles * mBubbleSize;
+        final float totalGapWidth = maxRowWidth - totalBubbleWidth;
+
+        final int gapCount = mMaxBubbles - 1;
+        final float gapWidth = totalGapWidth / gapCount;
+        return gapWidth;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
index f8b32c2..498330c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
@@ -38,6 +38,7 @@
 import com.google.android.collect.Sets;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
@@ -76,6 +77,7 @@
     }
 
     @Test
+    @Ignore
     public void testHierarchyChanges() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -101,6 +103,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateValueNotChained() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -127,11 +130,13 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateValueXChained() throws InterruptedException {
         testChainedTranslationAnimations();
     }
 
     @Test
+    @Ignore
     public void testSetEndActions() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -156,8 +161,8 @@
         });
 
         // Set end listeners for both x and y.
-        mLayout.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
-        mLayout.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
+        mTestableController.setEndActionForProperty(xEndAction, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(yEndAction, DynamicAnimation.TRANSLATION_Y);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -175,6 +180,7 @@
     }
 
     @Test
+    @Ignore
     public void testRemoveEndListeners() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -190,7 +196,7 @@
         });
 
         // Set the end listener.
-        mLayout.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
+        mTestableController.setEndActionForProperty(xEndListener, DynamicAnimation.TRANSLATION_X);
 
         // Animate x, and wait for it to finish.
         mTestableController.animationForChildAtIndex(0)
@@ -205,7 +211,7 @@
         mTestableController.animationForChildAtIndex(0)
                 .translationX(1000)
                 .start();
-        mLayout.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
+        mTestableController.removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
         xLatch.await(1, TimeUnit.SECONDS);
 
         // Make sure the end listener was not called.
@@ -213,6 +219,7 @@
     }
 
     @Test
+    @Ignore
     public void testSetController() throws InterruptedException {
         // Add the bubbles, then set the controller, to make sure that a controller added to an
         // already-initialized view works correctly.
@@ -269,6 +276,7 @@
     }
 
     @Test
+    @Ignore
     public void testArePropertiesAnimating() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -293,6 +301,7 @@
     }
 
     @Test
+    @Ignore
     public void testCancelAllAnimations() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -346,6 +355,7 @@
     }
 
     @Test
+    @Ignore
     public void testPhysicsAnimator() throws InterruptedException {
         mLayout.setActiveController(mTestableController);
         addOneMoreThanBubbleLimitBubbles();
@@ -390,6 +400,7 @@
     }
 
     @Test
+    @Ignore
     public void testAnimationsForChildrenFromIndex() throws InterruptedException {
         // Don't chain since we're going to invoke each animation independently.
         mTestableController.setChainedProperties(new HashSet<>());
@@ -415,6 +426,7 @@
     }
 
     @Test
+    @Ignore
     public void testAnimationsForChildrenFromIndex_noChildren() {
         mLayout.setActiveController(mTestableController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index f633f39..a5f2e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -68,7 +68,7 @@
     @Mock
     private DisplayCutout mCutout;
 
-    private int mMaxBubbles;
+    protected int mMaxBubbles;
 
     @Before
     public void setUp() throws Exception {
@@ -195,14 +195,13 @@
         private void setTestEndActionForProperty(
                 Runnable action, DynamicAnimation.ViewProperty property) {
             final Runnable realEndAction = mEndActionForProperty.get(property);
-
-            setEndActionForProperty(() -> {
+            mLayout.mEndActionForProperty.put(property, () -> {
                 if (realEndAction != null) {
                     realEndAction.run();
                 }
 
                 action.run();
-            }, property);
+            });
         }
 
         /** PhysicsPropertyAnimator that posts its animations to the main thread. */
@@ -219,6 +218,11 @@
                         property, view, value, startVel, startDelay, stiffness, dampingRatio,
                         afterCallbacks));
             }
+
+            @Override
+            protected void startPathAnimation() {
+                mMainThreadHandler.post(super::startPathAnimation);
+            }
         }
 
         /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 31a7d5a..d79128c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -339,10 +339,10 @@
 
         @Override
         protected void springFirstBubbleWithStackFollowing(DynamicAnimation.ViewProperty property,
-                SpringForce spring, float vel, float finalPosition) {
+                SpringForce spring, float vel, float finalPosition, Runnable... after) {
             mMainThreadHandler.post(() ->
                     super.springFirstBubbleWithStackFollowing(
-                            property, spring, vel, finalPosition));
+                            property, spring, vel, finalPosition, after));
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index 2073ae1..3561e34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -28,11 +28,13 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.sensors.ProximitySensor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -45,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;
@@ -55,6 +59,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mTestableLooper = TestableLooper.get(this);
         mHandler = new Handler(mTestableLooper.getLooper());
         mDeviceConfig = new DeviceConfigProxyFake();
@@ -71,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));
     }
 
@@ -80,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/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 9576cb2..c9b6790 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -54,6 +54,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.wakelock.SettableWakeLock;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -85,15 +86,15 @@
     @Mock
     private SettableWakeLock mMediaWakeLock;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
     private DozeParameters mDozeParameters;
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private TestableKeyguardSliceProvider mProvider;
     private boolean mIsZenMode;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        mKeyguardUpdateMonitor = mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mIsZenMode = false;
         mProvider = new TestableKeyguardSliceProvider();
         mProvider.attachInfo(getContext(), null);
@@ -102,6 +103,11 @@
         SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
     }
 
+    @After
+    public void tearDown() {
+        mProvider.onDestroy();
+    }
+
     @Test
     public void registersClockUpdate() {
         Assert.assertTrue("registerClockUpdate should have been called during initialization.",
@@ -269,11 +275,6 @@
         }
 
         @Override
-        public KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
-            return mKeyguardUpdateMonitor;
-        }
-
-        @Override
         protected String getFormattedDateLocked() {
             return super.getFormattedDateLocked() + mCounter++;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index f2292fd..07fbbcf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -97,6 +97,8 @@
                 mLooper.getLooper(),
                 mPluginManager, mTunerService, mAutoTiles, mDumpController);
         setUpTileFactory();
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), QSTileHost.TILES_SETTING,
+                "", ActivityManager.getCurrentUser());
     }
 
     private void setUpTileFactory() {
@@ -188,7 +190,8 @@
             // changed
             String newSetting = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                     TILES_SETTING, ActivityManager.getCurrentUser());
-            if (!previousSetting.equals(newSetting)) {
+            // newSetting is not null, as it has just been set.
+            if (!newSetting.equals(previousSetting)) {
                 onTuningChanged(TILES_SETTING, newSetting);
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
new file mode 100644
index 0000000..b51e716
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.UserSwitcherController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UserDetailViewAdapterTest : SysuiTestCase() {
+
+    @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+    @Mock private lateinit var mParent: ViewGroup
+    @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mOtherView: View
+    @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+    @Mock private lateinit var mUserInfo: UserInfo
+    @Mock private lateinit var mPicture: Bitmap
+    @Mock private lateinit var mLayoutInflater: LayoutInflater
+    private lateinit var adapter: UserDetailView.Adapter
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
+        `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
+                .thenReturn(mInflatedUserDetailItemView)
+        adapter = UserDetailView.Adapter(mContext, mUserSwitcherController)
+    }
+
+    private fun clickableTest(
+        current: Boolean,
+        guest: Boolean,
+        convertView: View,
+        shouldBeClickable: Boolean
+    ) {
+        val user = createUserRecord(current, guest)
+        val v = adapter.createUserDetailItemView(convertView, mParent, user)
+        if (shouldBeClickable) {
+            verify(v).setOnClickListener(adapter)
+        } else {
+            verify(v).setOnClickListener(null)
+        }
+    }
+
+    @Test
+    fun testGuestIsClickable_differentViews_notCurrent() {
+        clickableTest(false, true, mOtherView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_differentViews_Current() {
+        clickableTest(true, true, mOtherView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_sameView_notCurrent() {
+        clickableTest(false, true, mUserDetailItemView, true)
+    }
+
+    @Test
+    fun testGuestIsClickable_sameView_Current() {
+        clickableTest(true, true, mUserDetailItemView, true)
+    }
+
+    @Test
+    fun testNotGuestCurrentUserIsNotClickable_otherView() {
+        clickableTest(true, false, mOtherView, false)
+    }
+
+    @Test
+    fun testNotGuestCurrentUserIsNotClickable_sameView() {
+        clickableTest(true, false, mUserDetailItemView, false)
+    }
+
+    @Test
+    fun testNotGuestNotCurrentUserIsClickable_otherView() {
+        clickableTest(false, false, mOtherView, true)
+    }
+
+    @Test
+    fun testNotGuestNotCurrentUserIsClickable_sameView() {
+        clickableTest(false, false, mUserDetailItemView, true)
+    }
+
+    private fun createUserRecord(current: Boolean, guest: Boolean) =
+            UserSwitcherController.UserRecord(
+                    mUserInfo,
+                    mPicture,
+                    guest,
+                    current,
+                    false /* isAddUser */,
+                    false /* isRestricted */,
+                    true /* isSwitchToEnabled */)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
index 784d035..0b871e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotStubActivity.java
@@ -15,11 +15,11 @@
  */
 package com.android.systemui.screenshot;
 
-import com.android.systemui.R;
-
 import android.app.Activity;
 import android.os.Bundle;
 
+import com.android.systemui.tests.R;
+
 /**
  * A stub activity used in {@link ScreenshotTest}.
  */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index 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..0817ee9 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,7 +45,6 @@
 
 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;
@@ -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;
@@ -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();
@@ -127,72 +115,6 @@
     }
 
     @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);
-    }
-
-    @Test
     public void transientIndication_holdsWakeLock_whenDozing() {
         createController();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 690133a..a0a410d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -24,6 +24,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
@@ -66,6 +67,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                new Handler(TestableLooper.get(this).getLooper()));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
new file mode 100644
index 0000000..fcfdd11
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryBuilder.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Importance;
+import android.os.UserHandle;
+import android.service.notification.SnoozeCriterion;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.ArrayList;
+
+/**
+ * Combined builder for constructing a NotificationEntry and its associated StatusBarNotification
+ * and Ranking. Is largely a proxy for the SBN and Ranking builders, but does a little extra magic
+ * to make sure the keys match between the two, etc.
+ *
+ * Only for use in tests.
+ */
+public class NotificationEntryBuilder {
+    private final SbnBuilder mSbnBuilder = new SbnBuilder();
+    private final RankingBuilder mRankingBuilder = new RankingBuilder();
+    private StatusBarNotification mSbn = null;
+
+    public NotificationEntry build() {
+        StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
+        mRankingBuilder.setKey(sbn.getKey());
+        return new NotificationEntry(sbn, mRankingBuilder.build());
+    }
+
+    /**
+     * Sets the SBN directly. If set, causes all calls to delegated SbnBuilder methods to be
+     * ignored.
+     */
+    public NotificationEntryBuilder setSbn(@Nullable StatusBarNotification sbn) {
+        mSbn = sbn;
+        return this;
+    }
+
+    /* Delegated to SbnBuilder */
+
+    public NotificationEntryBuilder setPkg(String pkg) {
+        mSbnBuilder.setPkg(pkg);
+        return this;
+    }
+
+    public NotificationEntryBuilder setOpPkg(String opPkg) {
+        mSbnBuilder.setOpPkg(opPkg);
+        return this;
+    }
+
+    public NotificationEntryBuilder setId(int id) {
+        mSbnBuilder.setId(id);
+        return this;
+    }
+
+    public NotificationEntryBuilder setTag(String tag) {
+        mSbnBuilder.setTag(tag);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUid(int uid) {
+        mSbnBuilder.setUid(uid);
+        return this;
+    }
+
+    public NotificationEntryBuilder setInitialPid(int initialPid) {
+        mSbnBuilder.setInitialPid(initialPid);
+        return this;
+    }
+
+    public NotificationEntryBuilder setNotification(Notification notification) {
+        mSbnBuilder.setNotification(notification);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUser(UserHandle user) {
+        mSbnBuilder.setUser(user);
+        return this;
+    }
+
+    public NotificationEntryBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mSbnBuilder.setOverrideGroupKey(overrideGroupKey);
+        return this;
+    }
+
+    public NotificationEntryBuilder setPostTime(long postTime) {
+        mSbnBuilder.setPostTime(postTime);
+        return this;
+    }
+
+    /* Delegated to RankingBuilder */
+
+    public NotificationEntryBuilder setRank(int rank) {
+        mRankingBuilder.setRank(rank);
+        return this;
+    }
+
+    public NotificationEntryBuilder setMatchesInterruptionFilter(
+            boolean matchesInterruptionFilter) {
+        mRankingBuilder.setMatchesInterruptionFilter(matchesInterruptionFilter);
+        return this;
+    }
+
+    public NotificationEntryBuilder setVisibilityOverride(int visibilityOverride) {
+        mRankingBuilder.setVisibilityOverride(visibilityOverride);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSuppressedVisualEffects(int suppressedVisualEffects) {
+        mRankingBuilder.setSuppressedVisualEffects(suppressedVisualEffects);
+        return this;
+    }
+
+    public NotificationEntryBuilder setExplanation(CharSequence explanation) {
+        mRankingBuilder.setExplanation(explanation);
+        return this;
+    }
+
+    public NotificationEntryBuilder setAdditionalPeople(ArrayList<String> additionalPeople) {
+        mRankingBuilder.setAdditionalPeople(additionalPeople);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSnoozeCriteria(
+            ArrayList<SnoozeCriterion> snoozeCriteria) {
+        mRankingBuilder.setSnoozeCriteria(snoozeCriteria);
+        return this;
+    }
+
+    public NotificationEntryBuilder setCanShowBadge(boolean canShowBadge) {
+        mRankingBuilder.setCanShowBadge(canShowBadge);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSuspended(boolean suspended) {
+        mRankingBuilder.setSuspended(suspended);
+        return this;
+    }
+
+    public NotificationEntryBuilder setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
+        mRankingBuilder.setLastAudiblyAlertedMs(lastAudiblyAlertedMs);
+        return this;
+    }
+
+    public NotificationEntryBuilder setNoisy(boolean noisy) {
+        mRankingBuilder.setNoisy(noisy);
+        return this;
+    }
+
+    public NotificationEntryBuilder setCanBubble(boolean canBubble) {
+        mRankingBuilder.setCanBubble(canBubble);
+        return this;
+    }
+
+    public NotificationEntryBuilder setImportance(@Importance int importance) {
+        mRankingBuilder.setImportance(importance);
+        return this;
+    }
+
+    public NotificationEntryBuilder setUserSentiment(int userSentiment) {
+        mRankingBuilder.setUserSentiment(userSentiment);
+        return this;
+    }
+
+    public NotificationEntryBuilder setChannel(NotificationChannel channel) {
+        mRankingBuilder.setChannel(channel);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartActions(
+            ArrayList<Notification.Action> smartActions) {
+        mRankingBuilder.setSmartActions(smartActions);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartActions(Notification.Action... smartActions) {
+        mRankingBuilder.setSmartActions(smartActions);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartReplies(ArrayList<CharSequence> smartReplies) {
+        mRankingBuilder.setSmartReplies(smartReplies);
+        return this;
+    }
+
+    public NotificationEntryBuilder setSmartReplies(CharSequence... smartReplies) {
+        mRankingBuilder.setSmartReplies(smartReplies);
+        return this;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
new file mode 100644
index 0000000..33b0d2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+@VisibleForTesting
+public class NotificationEntryHelper {
+    public static ModifiedRankingBuilder modifyRanking(NotificationEntry entry) {
+        return new ModifiedRankingBuilder(entry);
+    }
+
+    public static ModifiedSbnBuilder modifySbn(NotificationEntry entry) {
+        return new ModifiedSbnBuilder(entry);
+    }
+
+    public static class ModifiedRankingBuilder extends RankingBuilder {
+        private final NotificationEntry mTarget;
+
+        private ModifiedRankingBuilder(NotificationEntry target) {
+            super(target.ranking());
+            mTarget = target;
+        }
+
+        @Override
+        public Ranking build() {
+            final Ranking ranking = super.build();
+            mTarget.setRanking(ranking);
+            return ranking;
+        }
+    }
+
+    public static class ModifiedSbnBuilder extends SbnBuilder {
+        private final NotificationEntry mTarget;
+
+        private ModifiedSbnBuilder(NotificationEntry target) {
+            super(target.sbn());
+            mTarget = target;
+        }
+
+        @Override
+        public StatusBarNotification build() {
+            final StatusBarNotification sbn = super.build();
+            mTarget.setNotification(sbn);
+            return sbn;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
new file mode 100644
index 0000000..350ab5a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.service.dreams.IDreamManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.NotificationFilter;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the interruption state provider which understands whether the system & notification
+ * is in a state allowing a particular notification to hun, pulse, or bubble.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class NotificationInterruptionStateProviderTest extends SysuiTestCase {
+
+    @Mock
+    PowerManager mPowerManager;
+    @Mock
+    IDreamManager mDreamManager;
+    @Mock
+    AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+    @Mock
+    NotificationFilter mNotificationFilter;
+    @Mock
+    StatusBarStateController mStatusBarStateController;
+    @Mock
+    NotificationPresenter mPresenter;
+    @Mock
+    HeadsUpManager mHeadsUpManager;
+    @Mock
+    NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+
+    private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mNotifInterruptionStateProvider =
+                new TestableNotificationInterruptionStateProvider(mContext,
+                        mPowerManager,
+                        mDreamManager,
+                        mAmbientDisplayConfiguration,
+                        mNotificationFilter,
+                        mStatusBarStateController);
+
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#canAlertCommon(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills group suppression check.
+     */
+    private void ensureStateForAlertCommon() {
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(false);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#canAlertAwakeCommon(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills launch fullscreen check.
+     */
+    private void ensureStateForAlertAwakeCommon() {
+        when(mPresenter.isDeviceInVrMode()).thenReturn(false);
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+        when(mDreamManager.isDreaming()).thenReturn(false);
+        when(mPowerManager.isScreenOn()).thenReturn(true);
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldHeadsUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & DND checks.
+     */
+    private void ensureStateForHeadsUpWhenDozing() {
+        ensureStateForAlertCommon();
+
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(true);
+    }
+
+    /**
+     * Sets up the state such that any requests to
+     * {@link NotificationInterruptionStateProvider#shouldBubbleUp(NotificationEntry)} will
+     * pass as long its provided NotificationEntry fulfills importance & bubble checks.
+     */
+    private void ensureStateForBubbleUp() {
+        ensureStateForAlertCommon();
+        ensureStateForAlertAwakeCommon();
+    }
+
+    /**
+     * Ensure that the disabled state is set correctly.
+     */
+    @Test
+    public void testDisableNotificationAlerts() {
+        // Enabled by default
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+
+        // Disable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isTrue();
+
+        // Enable alerts
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(false);
+        assertThat(mNotifInterruptionStateProvider.areNotificationAlertsDisabled()).isFalse();
+    }
+
+    /**
+     * Ensure that the disabled alert state effects whether HUNs are enabled.
+     */
+    @Test
+    public void testHunSettingsChange_enabled_butAlertsDisabled() {
+        // Set up but without a mock change observer
+        mNotifInterruptionStateProvider.setUpWithPresenter(
+                mPresenter,
+                mHeadsUpManager,
+                mHeadsUpSuppressor);
+
+        // HUNs enabled by default
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isTrue();
+
+        // Set alerts disabled
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+
+        // No more HUNs
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+    }
+
+    /**
+     * Alerts can happen.
+     */
+    @Test
+    public void testCanAlertCommon_true() {
+        ensureStateForAlertCommon();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isTrue();
+    }
+
+    /**
+     * Filtered out notifications don't alert.
+     */
+    @Test
+    public void testCanAlertCommon_false_filteredOut() {
+        ensureStateForAlertCommon();
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+    }
+
+    /**
+     * Grouped notifications have different alerting behaviours, sometimes the alert for a
+     * grouped notification may be suppressed {@link android.app.Notification#GROUP_ALERT_CHILDREN}.
+     */
+    @Test
+    public void testCanAlertCommon_false_suppressedForGroups() {
+        ensureStateForAlertCommon();
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(new Notification.Builder(getContext(), "a")
+                        .setGroup("a")
+                        .setGroupSummary(true)
+                        .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
+                        .build())
+                .setImportance(IMPORTANCE_DEFAULT)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.canAlertCommon(entry)).isFalse();
+    }
+
+    /**
+     * HUNs while dozing can happen.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_true() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    /**
+     * Ambient display can show HUNs for new notifications, this may be disabled.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_pulseDisabled() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mAmbientDisplayConfiguration.pulseOnNotificationEnabled(anyInt())).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is not in ambient display or sleeping then we don't HUN.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_notDozing() {
+        ensureStateForHeadsUpWhenDozing();
+        when(mStatusBarStateController.isDozing()).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND ambient effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_AMBIENT}.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_suppressingAmbient() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_AMBIENT)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_DEFAULT} don't
+     * get to pulse.
+     */
+    @Test
+    public void testShouldHeadsUpWhenDozing_false_lessImportant() {
+        ensureStateForHeadsUpWhenDozing();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_LOW);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Heads up can happen.
+     */
+    @Test
+    public void testShouldHeadsUp_true() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    /**
+     * Heads up notifications can be disabled in general.
+     */
+    @Test
+    public void testShouldHeadsUp_false_noHunsAllowed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Set alerts disabled, this should cause heads up to be false
+        mNotifInterruptionStateProvider.setDisableNotificationAlerts(true);
+        assertThat(mNotifInterruptionStateProvider.getUseHeadsUp()).isFalse();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is dozing, we don't show as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_dozing() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        when(mStatusBarStateController.isDozing()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification is a bubble, and the user is not on AOD / lockscreen, then
+     * the bubble is shown rather than the heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_bubble() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        // Bubble bit only applies to interruption when we're in the shade
+        when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(createBubble())).isFalse();
+    }
+
+    /**
+     * If we're not allowed to alert in general, we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_alertCommonFalse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * In DND HUN peek effects can be suppressed
+     * {@link android.app.NotificationManager.Policy#SUPPRESSED_EFFECT_PEEK}.
+     */
+    @Test
+    public void testShouldHeadsUp_false_suppressPeek() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setSuppressedVisualEffects(SUPPRESSED_EFFECT_PEEK)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * Notifications that are < {@link android.app.NotificationManager#IMPORTANCE_HIGH} don't get
+     * to show as a heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_lessImportant() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If the device is not in use then we shouldn't be shown as heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_deviceNotInUse() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+        // Device is not in use if screen is not on
+        when(mPowerManager.isScreenOn()).thenReturn(false);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+        // Also not in use if screen is on but we're showing screen saver / "dreaming"
+        when(mPowerManager.isDeviceIdleMode()).thenReturn(true);
+        when(mDreamManager.isDreaming()).thenReturn(true);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    /**
+     * If something wants to suppress this heads up, then it shouldn't be shown as a heads up.
+     */
+    @Test
+    public void testShouldHeadsUp_false_suppressed() throws RemoteException {
+        ensureStateForHeadsUpWhenAwake();
+        when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(false);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+        verify(mHeadsUpSuppressor).canHeadsUp(any(), any());
+    }
+
+    /**
+     * On screen alerts don't happen when the device is in VR Mode.
+     */
+    @Test
+    public void testCanAlertAwakeCommon__false_vrMode() {
+        ensureStateForAlertAwakeCommon();
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * On screen alerts don't happen when the notification is snoozed.
+     */
+    @Test
+    public void testCanAlertAwakeCommon_false_snoozedPackage() {
+        ensureStateForAlertAwakeCommon();
+        when(mHeadsUpManager.isSnoozed(any())).thenReturn(true);
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * On screen alerts don't happen when that package has just launched fullscreen.
+     */
+    @Test
+    public void testCanAlertAwakeCommon_false_justLaunchedFullscreen() {
+        ensureStateForAlertAwakeCommon();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+        entry.notifyFullScreenIntentLaunched();
+
+        assertThat(mNotifInterruptionStateProvider.canAlertAwakeCommon(entry)).isFalse();
+    }
+
+    /**
+     * Bubbles can happen.
+     */
+    @Test
+    public void testShouldBubbleUp_true() {
+        ensureStateForBubbleUp();
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isTrue();
+    }
+
+    /**
+     * If the notification doesn't have permission to bubble, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notAllowedToBubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createBubble();
+        modifyRanking(entry)
+                .setCanBubble(false)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification isn't a bubble, it should definitely not show as a bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_notABubble() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification doesn't have bubble metadata, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_invalidMetadata() {
+        ensureStateForBubbleUp();
+
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+        modifyRanking(entry)
+                .setCanBubble(true)
+                .build();
+        entry.sbn().getNotification().flags |= FLAG_BUBBLE;
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(entry)).isFalse();
+    }
+
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_alertAwakeCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // Make alert common return false by pretending we're in VR mode
+        when(mPresenter.isDeviceInVrMode()).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+    }
+
+    /**
+     * If the notification can't heads up in general, it shouldn't bubble.
+     */
+    @Test
+    public void shouldBubbleUp_false_alertCommonFalse() {
+        ensureStateForBubbleUp();
+
+        // Make canAlertCommon false by saying it's filtered out
+        when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+
+        assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
+    }
+
+    private NotificationEntry createBubble() {
+        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+                .setIntent(PendingIntent.getActivity(mContext, 0, new Intent(), 0))
+                .setIcon(Icon.createWithResource(mContext.getResources(), R.drawable.android))
+                .build();
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .setBubbleMetadata(data)
+                .build();
+        n.flags |= FLAG_BUBBLE;
+
+        return new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(IMPORTANCE_HIGH)
+                .setCanBubble(true)
+                .build();
+    }
+
+    private NotificationEntry createNotification(int importance) {
+        Notification n = new Notification.Builder(getContext(), "a")
+                .setContentTitle("title")
+                .setContentText("content text")
+                .build();
+
+        return new NotificationEntryBuilder()
+                .setPkg("a")
+                .setOpPkg("a")
+                .setTag("a")
+                .setNotification(n)
+                .setImportance(importance)
+                .build();
+    }
+
+    /**
+     * Testable class overriding constructor.
+     */
+    public class TestableNotificationInterruptionStateProvider extends
+            NotificationInterruptionStateProvider {
+
+        TestableNotificationInterruptionStateProvider(Context context,
+                PowerManager powerManager, IDreamManager dreamManager,
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter notificationFilter,
+                StatusBarStateController statusBarStateController) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration,
+                    notificationFilter,
+                    statusBarStateController);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 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 99428ec..de77af8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static org.mockito.Mockito.mock;
 
 import android.annotation.Nullable;
@@ -40,7 +42,6 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.systemui.R;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -50,6 +51,7 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.tests.R;
 
 /**
  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
@@ -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,23 +299,28 @@
         row.setGroupManager(mGroupManager);
         row.setHeadsUpManager(mHeadsUpManager);
         row.setAboveShelfChangedListener(aboveShelf -> {});
-        StatusBarNotification sbn = new StatusBarNotification(
-                pkg,
-                pkg,
-                mId++,
-                null /* tag */,
-                uid,
-                2000 /* initialPid */,
-                notification,
-                userHandle,
-                null /* overrideGroupKey */,
-                System.currentTimeMillis());
-        NotificationEntry entry = NotificationEntry.buildForTest(sbn);
+
+        final NotificationChannel channel =
+                new NotificationChannel(
+                        notification.getChannelId(),
+                        notification.getChannelId(),
+                        importance);
+        channel.setBlockableSystem(true);
+
+        NotificationEntry entry = new NotificationEntryBuilder()
+                .setPkg(pkg)
+                .setOpPkg(pkg)
+                .setId(mId++)
+                .setUid(uid)
+                .setInitialPid(2000)
+                .setNotification(notification)
+                .setUser(userHandle)
+                .setPostTime(System.currentTimeMillis())
+                .setChannel(channel)
+                .build();
+
         entry.setRow(row);
-        entry.createIcons(mContext, sbn);
-        entry.channel = new NotificationChannel(
-                notification.getChannelId(), notification.getChannelId(), importance);
-        entry.channel.setBlockableSystem(true);
+        entry.createIcons(mContext, entry.sbn());
         row.setEntry(entry);
         row.getNotificationInflater().addInflationFlags(extraInflationFlags);
         NotificationContentInflaterTest.runThenWaitForInflation(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index f63389c..d00be56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -30,6 +30,7 @@
 
 import org.junit.Test;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -199,11 +200,11 @@
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
             String key, List<Notification.Action> actions) {
-        return new NotificationUiAdjustment(key, actions, new CharSequence[0]);
+        return new NotificationUiAdjustment(key, actions, null);
     }
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
             String key, CharSequence[] replies) {
-        return new NotificationUiAdjustment(key, Collections.emptyList(), replies);
+        return new NotificationUiAdjustment(key, null, Arrays.asList(replies));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 54d8688..9e72504 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -41,7 +41,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -110,8 +110,9 @@
         mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
                 mHandler, mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
                 mock(StatusBarStateControllerImpl.class), mEntryManager,
-                () -> mShadeController, new BubbleData(mContext),
+                () -> mShadeController,
                 mock(KeyguardBypassController.class),
+                mock(BubbleController.class),
                 mock(DynamicPrivacyController.class));
         Dependency.get(InitController.class).executePostInitTasks();
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
@@ -119,7 +120,9 @@
 
     private NotificationEntry createEntry() throws Exception {
         ExpandableNotificationRow row = mHelper.createRow();
-        NotificationEntry entry = 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
new file mode 100644
index 0000000..820f465
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RankingBuilder.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager.Importance;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.SnoozeCriterion;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Standard builder class for Ranking objects. For use in tests that need to craft the underlying
+ * Ranking object of a NotificationEntry.
+ */
+public class RankingBuilder {
+    private String mKey = "test_key";
+    private int mRank = 0;
+    private boolean mMatchesInterruptionFilter = false;
+    private int mVisibilityOverride = 0;
+    private int mSuppressedVisualEffects = 0;
+    @Importance private int mImportance = 0;
+    private CharSequence mExplanation = "test_explanation";
+    private String mOverrideGroupKey = null;
+    private NotificationChannel mChannel = null;
+    private ArrayList<String> mAdditionalPeople = null;
+    private ArrayList<SnoozeCriterion> mSnoozeCriteria = null;
+    private boolean mCanShowBadge = false;
+    private int mUserSentiment = 0;
+    private boolean mIsSuspended = false;
+    private long mLastAudiblyAlertedMs = 0;
+    private boolean mNoisy = false;
+    private ArrayList<Notification.Action> mSmartActions = new ArrayList<>();
+    private ArrayList<CharSequence> mSmartReplies = new ArrayList<>();
+    private boolean mCanBubble = false;
+    private boolean mIsVisuallyInterruptive = false;
+
+    public RankingBuilder() {
+    }
+
+    public RankingBuilder(Ranking ranking) {
+        mKey = ranking.getKey();
+        mRank = ranking.getRank();
+        mMatchesInterruptionFilter = ranking.matchesInterruptionFilter();
+        mVisibilityOverride = ranking.getVisibilityOverride();
+        mSuppressedVisualEffects = ranking.getSuppressedVisualEffects();
+        mImportance = ranking.getImportance();
+        mExplanation = ranking.getImportanceExplanation();
+        mOverrideGroupKey = ranking.getOverrideGroupKey();
+        mChannel = ranking.getChannel();
+        mAdditionalPeople = copyList(ranking.getAdditionalPeople());
+        mSnoozeCriteria = copyList(ranking.getSnoozeCriteria());
+        mCanShowBadge = ranking.canShowBadge();
+        mUserSentiment = ranking.getUserSentiment();
+        mIsSuspended = ranking.isSuspended();
+        mLastAudiblyAlertedMs = ranking.getLastAudiblyAlertedMillis();
+        mNoisy = ranking.isNoisy();
+        mSmartActions = copyList(ranking.getSmartActions());
+        mSmartReplies = copyList(ranking.getSmartReplies());
+        mCanBubble = ranking.canBubble();
+    }
+
+    public Ranking build() {
+        final Ranking ranking = new Ranking();
+        ranking.populate(
+                mKey,
+                mRank,
+                mMatchesInterruptionFilter,
+                mVisibilityOverride,
+                mSuppressedVisualEffects,
+                mImportance,
+                mExplanation,
+                mOverrideGroupKey,
+                mChannel,
+                mAdditionalPeople,
+                mSnoozeCriteria,
+                mCanShowBadge,
+                mUserSentiment,
+                mIsSuspended,
+                mLastAudiblyAlertedMs,
+                mNoisy,
+                mSmartActions,
+                mSmartReplies,
+                mCanBubble,
+                mIsVisuallyInterruptive);
+        return ranking;
+    }
+
+    public RankingBuilder setKey(String key) {
+        mKey = key;
+        return this;
+    }
+
+    public RankingBuilder setRank(int rank) {
+        mRank = rank;
+        return this;
+    }
+
+    public RankingBuilder setMatchesInterruptionFilter(boolean matchesInterruptionFilter) {
+        mMatchesInterruptionFilter = matchesInterruptionFilter;
+        return this;
+    }
+
+    public RankingBuilder setVisibilityOverride(int visibilityOverride) {
+        mVisibilityOverride = visibilityOverride;
+        return this;
+    }
+
+    public RankingBuilder setSuppressedVisualEffects(int suppressedVisualEffects) {
+        mSuppressedVisualEffects = suppressedVisualEffects;
+        return this;
+    }
+
+    public RankingBuilder setExplanation(CharSequence explanation) {
+        mExplanation = explanation;
+        return this;
+    }
+
+    public RankingBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mOverrideGroupKey = overrideGroupKey;
+        return this;
+    }
+
+    public RankingBuilder setAdditionalPeople(ArrayList<String> additionalPeople) {
+        mAdditionalPeople = additionalPeople;
+        return this;
+    }
+
+    public RankingBuilder setSnoozeCriteria(
+            ArrayList<SnoozeCriterion> snoozeCriteria) {
+        mSnoozeCriteria = snoozeCriteria;
+        return this;
+    }
+
+    public RankingBuilder setCanShowBadge(boolean canShowBadge) {
+        mCanShowBadge = canShowBadge;
+        return this;
+    }
+
+    public RankingBuilder setSuspended(boolean suspended) {
+        mIsSuspended = suspended;
+        return this;
+    }
+
+    public RankingBuilder setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
+        mLastAudiblyAlertedMs = lastAudiblyAlertedMs;
+        return this;
+    }
+
+    public RankingBuilder setNoisy(boolean noisy) {
+        mNoisy = noisy;
+        return this;
+    }
+
+    public RankingBuilder setCanBubble(boolean canBubble) {
+        mCanBubble = canBubble;
+        return this;
+    }
+
+    public RankingBuilder setImportance(@Importance int importance) {
+        mImportance = importance;
+        return this;
+    }
+
+    public RankingBuilder setUserSentiment(int userSentiment) {
+        mUserSentiment = userSentiment;
+        return this;
+    }
+
+    public RankingBuilder setChannel(NotificationChannel channel) {
+        mChannel = channel;
+        return this;
+    }
+
+    public RankingBuilder setSmartActions(@NonNull ArrayList<Notification.Action> smartActions) {
+        mSmartActions = smartActions;
+        return this;
+    }
+
+    public RankingBuilder setSmartActions(Notification.Action... smartActions) {
+        mSmartActions = new ArrayList<>(Arrays.asList(smartActions));
+        return this;
+    }
+
+    public RankingBuilder setSmartReplies(@NonNull ArrayList<CharSequence> smartReplies) {
+        mSmartReplies = smartReplies;
+        return this;
+    }
+
+    public RankingBuilder setSmartReplies(CharSequence... smartReplies) {
+        mSmartReplies = new ArrayList<>(Arrays.asList(smartReplies));
+        return this;
+    }
+
+    private static <E> ArrayList<E> copyList(List<E> list) {
+        if (list == null) {
+            return null;
+        } else {
+            return new ArrayList<>(list);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
new file mode 100644
index 0000000..9bc962c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SbnBuilder.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.app.Notification;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+
+/**
+ * Convenience builder for {@link StatusBarNotification} since its constructor is terrifying.
+ *
+ * Only for use in tests.
+ */
+public class SbnBuilder {
+    private String mPkg = "test_pkg";
+    private String mOpPkg;
+    private int mId;
+    private String mTag;
+    private int mUid;
+    private int mInitialPid;
+    private Notification mNotification = new Notification();
+    private UserHandle mUser = UserHandle.of(0);
+    private String mOverrideGroupKey;
+    private long mPostTime;
+
+    public SbnBuilder() {
+    }
+
+    public SbnBuilder(StatusBarNotification source) {
+        mPkg = source.getPackageName();
+        mOpPkg = source.getOpPkg();
+        mId = source.getId();
+        mTag = source.getTag();
+        mUid = source.getUid();
+        mInitialPid = source.getInitialPid();
+        mNotification = source.getNotification();
+        mUser = source.getUser();
+        mOverrideGroupKey = source.getOverrideGroupKey();
+        mPostTime = source.getPostTime();
+    }
+
+    public StatusBarNotification build() {
+        return new StatusBarNotification(
+                mPkg,
+                mOpPkg,
+                mId,
+                mTag,
+                mUid,
+                mInitialPid,
+                mNotification,
+                mUser,
+                mOverrideGroupKey,
+                mPostTime);
+    }
+
+    public SbnBuilder setPkg(String pkg) {
+        mPkg = pkg;
+        return this;
+    }
+
+    public SbnBuilder setOpPkg(String opPkg) {
+        mOpPkg = opPkg;
+        return this;
+    }
+
+    public SbnBuilder setId(int id) {
+        mId = id;
+        return this;
+    }
+
+    public SbnBuilder setTag(String tag) {
+        mTag = tag;
+        return this;
+    }
+
+    public SbnBuilder setUid(int uid) {
+        mUid = uid;
+        return this;
+    }
+
+    public SbnBuilder setInitialPid(int initialPid) {
+        mInitialPid = initialPid;
+        return this;
+    }
+
+    public SbnBuilder setNotification(Notification notification) {
+        mNotification = notification;
+        return this;
+    }
+
+    public SbnBuilder setUser(UserHandle user) {
+        mUser = user;
+        return this;
+    }
+
+    public SbnBuilder setOverrideGroupKey(String overrideGroupKey) {
+        mOverrideGroupKey = overrideGroupKey;
+        return this;
+    }
+
+    public SbnBuilder setPostTime(long postTime) {
+        mPostTime = postTime;
+        return this;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 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/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
index 7eeae67..e6287e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
@@ -38,8 +38,8 @@
 import androidx.palette.graphics.Palette;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 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 ed719d9..657ec61d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -27,10 +27,13 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_CHANNEL;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_IMPORTANCE;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_RANK;
 import static com.android.systemui.statusbar.notification.collection.NotificationDataTest.TestableNotificationData.OVERRIDE_VIS_EFFECTS;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 
 import static junit.framework.Assert.assertEquals;
 
@@ -47,6 +50,7 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
@@ -70,7 +74,10 @@
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -83,9 +90,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @SmallTest
@@ -98,8 +103,8 @@
     private static final NotificationChannel NOTIFICATION_CHANNEL =
             new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
 
-    private final StatusBarNotification mMockStatusBarNotification =
-            mock(StatusBarNotification.class);
+    private NotificationEntry mEntry;
+
     @Mock
     ForegroundServiceController mFsc;
     @Mock
@@ -113,9 +118,10 @@
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
-        when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
-        when(mMockStatusBarNotification.cloneLight()).thenReturn(mMockStatusBarNotification);
-        when(mMockStatusBarNotification.getKey()).thenReturn("mock_key");
+
+        mEntry = new NotificationEntryBuilder()
+                .setUid(UID_NORMAL)
+                .build();
 
         when(mMockPackageManager.checkUidPermission(
                 eq(Manifest.permission.NOTIFICATION_DURING_SETUP),
@@ -133,7 +139,7 @@
         mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
         when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
         when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
-        mNotificationData = new TestableNotificationData();
+        mNotificationData = new TestableNotificationData(mContext);
         mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class));
         mRow = new NotificationTestHelper(getContext()).createRow();
         Dependency.get(InitController.class).executePostInitTasks();
@@ -145,7 +151,7 @@
         override.putParcelable(OVERRIDE_CHANNEL, NOTIFICATION_CHANNEL);
         mNotificationData.rankingOverrides.put(mRow.getEntry().key, override);
         mNotificationData.add(mRow.getEntry());
-        assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel);
+        assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().getChannel());
     }
 
     @Test
@@ -231,57 +237,57 @@
     public void testIsExemptFromDndVisualSuppression_foreground() {
         initStatusBarNotification(false);
 
-        Notification n = mMockStatusBarNotification.getNotification();
-        n.flags = Notification.FLAG_FOREGROUND_SERVICE;
-        NotificationEntry entry = 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(NOTIFICATION_CHANNEL, entry.channel);
-        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
-        assertEquals(snoozeCriterions, entry.snoozeCriteria);
+        assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
+        assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
+        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.getUserSentiment());
+        assertEquals(snoozeCriterions, entry.getSnoozeCriteria());
     }
 
     @Test
@@ -366,10 +379,15 @@
         Notification notification = new Notification.Builder(mContext, "test")
                 .addExtras(bundle)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0, "tag", 0, 0,
-                notification, mContext.getUser(), "", 0);
 
-        NotificationEntry entry = 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 d526d10..c56a168 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
 import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
@@ -327,8 +328,11 @@
         // Give each child a unique channel id/name.
         int i = 0;
         for (ExpandableNotificationRow childRow : childRows) {
-            childRow.getEntry().channel =
-                    new NotificationChannel("id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT);
+            modifyRanking(childRow.getEntry())
+                    .setChannel(
+                            new NotificationChannel(
+                                    "id" + i, "dinnertime" + i, IMPORTANCE_DEFAULT))
+                    .build();
             i++;
         }
 
@@ -364,7 +368,7 @@
     public void testGetIsNonblockable_oemLocked() throws Exception {
         ExpandableNotificationRow row =
                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
-        row.getEntry().channel.setImportanceLockedByOEM(true);
+        row.getEntry().getChannel().setImportanceLockedByOEM(true);
 
         assertTrue(row.getIsNonblockable());
     }
@@ -373,7 +377,7 @@
     public void testGetIsNonblockable_criticalDeviceFunction() throws Exception {
         ExpandableNotificationRow row =
                 mNotificationTestHelper.createRow(mNotificationTestHelper.createNotification());
-        row.getEntry().channel.setImportanceLockedByCriticalDeviceFunction(true);
+        row.getEntry().getChannel().setImportanceLockedByCriticalDeviceFunction(true);
 
         assertTrue(row.getIsNonblockable());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 3c91b3f..6d64395 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -21,6 +21,8 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -41,6 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -73,6 +76,7 @@
     public void setUp() {
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
+        mDependency.injectMockDependency(BubbleController.class);
         when(mGutsManager.openGuts(
                 any(View.class),
                 anyInt(),
@@ -82,6 +86,7 @@
         when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem);
         mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
+        mDependency.injectMockDependency(BubbleController.class);
 
         mHelper = new NotificationTestHelper(mContext);
 
@@ -126,7 +131,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_shown() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
 
@@ -138,11 +145,16 @@
         ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
         int i = 0;
         for (ExpandableNotificationRow childRow : groupRow.getNotificationChildren()) {
-            childRow.getEntry().channel =
-                    new NotificationChannel(Integer.toString(i++), "", IMPORTANCE_DEFAULT);
+            modifyRanking(childRow.getEntry())
+                    .setChannel(
+                            new NotificationChannel(
+                                    Integer.toString(i++), "", IMPORTANCE_DEFAULT))
+                    .build();
         }
 
-        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(groupRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
 
@@ -152,7 +164,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception {
         ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
-        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(groupRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
 
@@ -167,7 +181,9 @@
         // as other factors such as view expansion may cause us to get the parent row back instead
         // of the child row.
         ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
-        childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(childRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         assertFalse(childRow.getIsNonblockable());
 
         assertTrue(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
@@ -178,7 +194,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_notShownDueToNeutralUserSentiment() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEUTRAL;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEUTRAL)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -187,7 +205,9 @@
     public void testPerhapsShowBlockingHelper_notShownDueToPositiveUserSentiment()
             throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_POSITIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_POSITIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -195,7 +215,9 @@
     @Test
     public void testPerhapsShowBlockingHelper_notShownDueToShadeVisibility() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         // Hide the shade
         mBlockingHelperManager.setNotificationShadeExpanded(0f);
 
@@ -206,7 +228,9 @@
     public void testPerhapsShowBlockingHelper_notShownDueToNonblockability() throws Exception {
         ExpandableNotificationRow row = createBlockableRowSpy();
         when(row.getIsNonblockable()).thenReturn(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(row, mMenuRow));
     }
@@ -219,7 +243,9 @@
         // as other factors such as view expansion may cause us to get the parent row back instead
         // of the child row.
         ExpandableNotificationRow childRow = groupRow.getChildrenContainer().getViewAtPosition(0);
-        childRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(childRow.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
 
         assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(childRow, mMenuRow));
     }
@@ -227,7 +253,9 @@
     @Test
     public void testBlockingHelperShowAndDismiss() throws Exception{
         ExpandableNotificationRow row = createBlockableRowSpy();
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.isAttachedToWindow()).thenReturn(true);
 
         // Show check
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 2ec125e..ccadcc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -23,7 +23,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -44,12 +43,12 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 7959484..db6b613 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -22,6 +22,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
+import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -312,7 +314,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -341,7 +345,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(false);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -370,8 +376,10 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
-        row.getEntry().importance = IMPORTANCE_DEFAULT;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .setImportance(IMPORTANCE_DEFAULT)
+                .build();
         row.getEntry().setIsHighPriority(true);
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
@@ -401,7 +409,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(false);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
         when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
@@ -431,7 +441,9 @@
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
         row.setBlockingHelperShowing(true);
-        row.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+        modifyRanking(row.getEntry())
+                .setUserSentiment(USER_SENTIMENT_NEGATIVE)
+                .build();
         when(row.getIsNonblockable()).thenReturn(false);
         StatusBarNotification statusBarNotification = row.getStatusBarNotification();
 
@@ -498,7 +510,9 @@
 
         try {
             ExpandableNotificationRow row = mHelper.createRow(nb.build());
-            row.getEntry().channel = mTestNotificationChannel;
+            modifyRanking(row.getEntry())
+                    .setChannel(mTestNotificationChannel)
+                    .build();
             return row;
         } catch (Exception e) {
             fail();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 98e1692..9eba4eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -28,9 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.NotificationChannel;
 import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -40,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
@@ -60,9 +59,7 @@
     public void setup() {
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
         mRow = mock(ExpandableNotificationRow.class);
-        NotificationEntry entry = NotificationEntry.buildForTest(
-                mock(StatusBarNotification.class));
-        entry.channel = mock(NotificationChannel.class);
+        NotificationEntry entry = new NotificationEntryBuilder().build();
         when(mRow.getEntry()).thenReturn(entry);
     }
 
@@ -72,7 +69,6 @@
                 NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
     }
 
-
     @Test
     public void testAttachDetach() {
         NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 63e18ce..49a6410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -24,10 +24,10 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.tests.R;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 524ad85..addceb5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -64,7 +64,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mRoundnessManager = new NotificationRoundnessManager(mBypassController);
+        mRoundnessManager = new NotificationRoundnessManager(mBypassController, mContext);
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
         mFirst = testHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 59d0f91..56ed0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -74,7 +77,7 @@
                         mActivityStarterDelegate,
                         mStatusBarStateController,
                         mConfigurationController,
-                        true);
+                        2);
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
                 .thenReturn(new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
@@ -263,8 +266,8 @@
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
                     when(notifRow.getEntry().isHighPriority())
                             .thenReturn(children[i] == ChildType.HIPRI);
-                    when(notifRow.getEntry().isTopBucket())
-                            .thenReturn(children[i] == ChildType.HIPRI);
+                    when(notifRow.getEntry().getBucket()).thenReturn(
+                            children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
                     child = notifRow;
                     break;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/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 cd60e47..c51263f 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
@@ -95,6 +95,7 @@
     public void setup() {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
         MockitoAnnotations.initMocks(this);
+        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
         DejankUtils.setImmediate(true);
         final ViewGroup container = new FrameLayout(getContext());
         when(mKeyguardHostView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
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 a96efd7..219aef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -21,23 +21,28 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.StatusBarManager;
+import android.hardware.biometrics.BiometricSourceType;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -50,7 +55,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
@@ -100,7 +104,11 @@
     @Mock
     private KeyguardAffordanceHelper mAffordanceHelper;
     @Mock
+    private KeyguardUpdateMonitor mUpdateMonitor;
+    @Mock
     private FalsingManager mFalsingManager;
+    @Mock
+    private KeyguardBypassController mKeyguardBypassController;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
@@ -111,22 +119,21 @@
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mStatusBarStateController);
+        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
         mDependency.injectMockDependency(ConfigurationController.class);
         mDependency.injectMockDependency(ZenModeController.class);
-        KeyguardBypassController bypassController = new KeyguardBypassController(mContext,
-                mock(TunerService.class), mStatusBarStateController,
-                mock(NotificationLockscreenUserManager.class));
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(mContext,
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(),
-                        bypassController);
+                        mKeyguardBypassController);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
-                bypassController, mHeadsUpManager, mock(NotificationRoundnessManager.class));
+                mKeyguardBypassController, mHeadsUpManager,
+                mock(NotificationRoundnessManager.class), mStatusBarStateController);
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
-                bypassController);
+                mKeyguardBypassController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
         mNotificationPanelView.setBar(mPanelBar);
 
@@ -186,6 +193,20 @@
         assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
     }
 
+    @Test
+    public void testKeyguardStatusBarVisibility_hiddenForBypass() {
+        when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
+        verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+
+        when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+        mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
+        verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
+    }
+
     private class TestableNotificationPanelView extends NotificationPanelView {
         TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
                 PulseExpansionHandler expansionHandler,
@@ -195,7 +216,7 @@
                             SystemUIFactory.getInstance().getRootComponent()),
                     coordinator, expansionHandler, mock(DynamicPrivacyController.class),
                     bypassController,
-                    mFalsingManager);
+                    mFalsingManager, mock(PluginManager.class));
             mNotificationStackScroller = mNotificationStackScrollLayout;
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
             mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 97ad47e..5d3cdc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
+import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -45,6 +45,7 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -69,6 +70,7 @@
     private SynchronousScrimController mScrimController;
     private ScrimView mScrimBehind;
     private ScrimView mScrimInFront;
+    private ScrimView mScrimForBubble;
     private ScrimState mScrimState;
     private float mScrimBehindAlpha;
     private GradientColors mScrimInFrontColor;
@@ -84,14 +86,17 @@
     public void setup() {
         mScrimBehind = spy(new ScrimView(getContext()));
         mScrimInFront = new ScrimView(getContext());
+        mScrimForBubble = new ScrimView(getContext());
         mWakeLock = mock(WakeLock.class);
         mAlarmManager = mock(AlarmManager.class);
         mAlwaysOnEnabled = true;
         mDozeParamenters = mock(DozeParameters.class);
         mLooper = TestableLooper.get(this);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
         when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true);
         mScrimController = new SynchronousScrimController(mScrimBehind, mScrimInFront,
+                mScrimForBubble,
                 (scrimState, scrimBehindAlpha, scrimInFrontColor) -> {
                     mScrimState = scrimState;
                     mScrimBehindAlpha = scrimBehindAlpha;
@@ -114,21 +119,28 @@
     public void transitionToKeyguard() {
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
     public void transitionToAod_withRegularWallpaper() {
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -136,14 +148,18 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // Pulsing notification should conserve AOD wallpaper.
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -152,11 +168,14 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -166,11 +185,14 @@
         mScrimController.finishAnimationsImmediately();
         mScrimController.setHasBackdrop(true);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible with tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -179,27 +201,32 @@
         mScrimController.transitionTo(ScrimState.KEYGUARD);
         mScrimController.setAodFrontScrimAlpha(0.5f);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... but that it does take effect once we enter the AOD state.
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be semi-transparent
-        // Back scrim should be visible
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and that if we set it while we're in AOD, it does take immediate effect.
         mScrimController.setAodFrontScrimAlpha(1f);
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and make sure we recall the previous front scrim alpha even if we transition away
         // for a bit.
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_OPAQUE, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(OPAQUE /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         // ... and alpha updates should be completely ignored if always_on is off.
         // Passing it forward would mess up the wake-up transition.
@@ -216,27 +243,46 @@
     }
 
     @Test
-    public void transitionToPulsing() {
+    public void transitionToPulsing_withFrontAlphaUpdates() {
         // Pre-condition
         // Need to go to AoD first because PULSING doesn't change
         // the back scrim opacity - otherwise it would hide AoD wallpapers.
         mScrimController.setWallpaperSupportsAmbientMode(false);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent, but tinted
         // Back scrim should be semi-transparent so the user can see the wallpaper
         // Pulse callback should have been invoked
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true /* tinted */);
-        assertScrimTint(mScrimInFront, true /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                false /* bubble */);
+
+        // ... and when ambient goes dark, front scrim should be semi-transparent
+        mScrimController.setAodFrontScrimAlpha(0.5f);
+        mScrimController.finishAnimationsImmediately();
+        // Front scrim should be semi-transparent
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                OPAQUE /* back */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.setWakeLockScreenSensorActive(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        // Reset value since enums are static.
+        mScrimController.setAodFrontScrimAlpha(0f);
     }
 
     @Test
@@ -245,8 +291,13 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -255,8 +306,12 @@
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be visible without tint
-        assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+        assertScrimAlpha(SEMI_TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
     }
 
     @Test
@@ -264,15 +319,19 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimBehind, false /* tinted */);
-        assertScrimTint(mScrimInFront, false /* tinted */);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
 
         // Back scrim should be visible after start dragging
         mScrimController.setPanelExpansion(0.5f);
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                SEMI_TRANSPARENT /* back */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -280,12 +339,19 @@
         mScrimController.transitionTo(ScrimState.BUBBLE_EXPANDED);
         mScrimController.finishAnimationsImmediately();
 
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false /* bubble */);
+
         // Front scrim should be transparent
-        Assert.assertEquals(ScrimController.VISIBILITY_FULLY_TRANSPARENT,
+        Assert.assertEquals(ScrimController.TRANSPARENT,
                 mScrimInFront.getViewAlpha(), 0.0f);
         // Back scrim should be visible
         Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
                 mScrimBehind.getViewAlpha(), 0.0f);
+        // Bubble scrim should be visible
+        Assert.assertEquals(ScrimController.GRADIENT_SCRIM_ALPHA_BUSY,
+                mScrimBehind.getViewAlpha(), 0.0f);
     }
 
     @Test
@@ -354,16 +420,22 @@
         mScrimController.setPanelExpansion(0f);
         mScrimController.finishAnimationsImmediately();
         mScrimController.transitionTo(ScrimState.UNLOCKED);
-        // Immediately tinted after the transition starts
-        assertScrimTint(mScrimInFront, true /* tinted */);
-        assertScrimTint(mScrimBehind, true /* tinted */);
+
+        // Immediately tinted black after the transition starts
+        assertScrimTint(true /* front */,
+                true /* behind */,
+                true  /* bubble */);
+
         mScrimController.finishAnimationsImmediately();
-        // Front scrim should be transparent
-        // Back scrim should be transparent
-        // Neither scrims should be tinted anymore after the animation.
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
-        assertScrimTint(mScrimInFront, false /* tinted */);
-        assertScrimTint(mScrimBehind, false /* tinted */);
+
+        // All scrims should be transparent at the end of fade transition.
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
+
+        assertScrimTint(false /* front */,
+                false /* behind */,
+                false  /* bubble */);
     }
 
     @Test
@@ -378,9 +450,11 @@
                         // Front scrim should be black in the middle of the transition
                         Assert.assertTrue("Scrim should be visible during transition. Alpha: "
                                 + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
-                        assertScrimTint(mScrimInFront, true /* tinted */);
+                        assertScrimTint(true /* front */,
+                                true /* behind */,
+                                true /* bubble */);
                         Assert.assertSame("Scrim should be visible during transition.",
-                                mScrimVisibility, VISIBILITY_FULLY_OPAQUE);
+                                mScrimVisibility, OPAQUE);
                     }
                 });
         mScrimController.finishAnimationsImmediately();
@@ -588,11 +662,15 @@
         mScrimController.setKeyguardOccluded(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -600,11 +678,15 @@
         mScrimController.setWallpaperSupportsAmbientMode(true);
         mScrimController.transitionTo(ScrimState.AOD);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                TRANSPARENT /* behind */,
+                TRANSPARENT /* bubble */);
 
         mScrimController.setKeyguardOccluded(true);
         mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+        assertScrimAlpha(TRANSPARENT /* front */,
+                OPAQUE /* behind */,
+                TRANSPARENT /* bubble */);
     }
 
     @Test
@@ -643,33 +725,68 @@
                 mScrimInFront.getDefaultFocusHighlightEnabled());
         Assert.assertFalse("Scrim shouldn't have focus highlight",
                 mScrimBehind.getDefaultFocusHighlightEnabled());
+        Assert.assertFalse("Scrim shouldn't have focus highlight",
+                mScrimForBubble.getDefaultFocusHighlightEnabled());
     }
 
-    private void assertScrimTint(ScrimView scrimView, boolean tinted) {
-        final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
-        final String name = scrimView == mScrimInFront ? "front" : "back";
+    private void assertScrimTint(boolean front, boolean behind, boolean bubble) {
         Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
-                +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
-                tinted, viewIsTinted);
+                        + " with scrim: " + getScrimName(mScrimInFront) + " and tint: "
+                        + Integer.toHexString(mScrimInFront.getTint()),
+                front, mScrimInFront.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimBehind) + " and tint: "
+                        + Integer.toHexString(mScrimBehind.getTint()),
+                behind, mScrimBehind.getTint() != Color.TRANSPARENT);
+
+        Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+                        + " with scrim: " + getScrimName(mScrimForBubble) + " and tint: "
+                        + Integer.toHexString(mScrimForBubble.getTint()),
+                bubble, mScrimForBubble.getTint() != Color.TRANSPARENT);
     }
 
-    private void assertScrimVisibility(int inFront, int behind) {
-        boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT;
-        Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
-                + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0);
-        Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
-                + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0);
-
-        final int visibility;
-        if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) {
-            visibility = VISIBILITY_FULLY_OPAQUE;
-        } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) {
-            visibility = VISIBILITY_SEMI_TRANSPARENT;
-        } else {
-            visibility = VISIBILITY_FULLY_TRANSPARENT;
+    private String getScrimName(ScrimView scrim) {
+        if (scrim == mScrimInFront) {
+            return "front";
+        } else if (scrim == mScrimBehind) {
+            return "back";
+        } else if (scrim == mScrimForBubble) {
+            return "bubble";
         }
-        Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility);
+        return "unknown_scrim";
+    }
+
+    private void assertScrimAlpha(int front, int behind, int bubble) {
+        // Check single scrim visibility.
+        Assert.assertEquals("Unexpected front scrim alpha: "
+                        + mScrimInFront.getViewAlpha(),
+                front != TRANSPARENT /* expected */,
+                mScrimInFront.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals("Unexpected back scrim alpha: "
+                        + mScrimBehind.getViewAlpha(),
+                behind != TRANSPARENT /* expected */,
+                mScrimBehind.getViewAlpha() > TRANSPARENT /* actual */);
+
+        Assert.assertEquals(
+                "Unexpected bubble scrim alpha: "
+                        + mScrimForBubble.getViewAlpha(), /* message */
+                bubble != TRANSPARENT /* expected */,
+                mScrimForBubble.getViewAlpha() > TRANSPARENT /* actual */);
+
+        // Check combined scrim visibility.
+        final int visibility;
+        if (front == OPAQUE || behind == OPAQUE || bubble == OPAQUE) {
+            visibility = OPAQUE;
+        } else if (front > TRANSPARENT || behind > TRANSPARENT || bubble > TRANSPARENT) {
+            visibility = SEMI_TRANSPARENT;
+        } else {
+            visibility = TRANSPARENT;
+        }
+        Assert.assertEquals("Invalid visibility.",
+                visibility /* expected */,
+                mScrimVisibility);
     }
 
     /**
@@ -681,11 +798,12 @@
         boolean mOnPreDrawCalled;
 
         SynchronousScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
+                ScrimView scrimForBubble,
                 TriConsumer<ScrimState, Float, GradientColors> scrimStateListener,
                 Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters,
                 AlarmManager alarmManager, KeyguardMonitor keyguardMonitor) {
-            super(scrimBehind, scrimInFront, scrimStateListener, scrimVisibleListener,
-                    dozeParameters, alarmManager, keyguardMonitor);
+            super(scrimBehind, scrimInFront, scrimForBubble, scrimStateListener,
+                    scrimVisibleListener, dozeParameters, alarmManager, keyguardMonitor);
         }
 
         @Override
@@ -696,13 +814,14 @@
 
         void finishAnimationsImmediately() {
             boolean[] animationFinished = {false};
-            setOnAnimationFinished(()-> animationFinished[0] = true);
+            setOnAnimationFinished(() -> animationFinished[0] = true);
             // Execute code that will trigger animations.
             onPreDraw();
             // Force finish all animations.
             mLooper.processAllMessages();
             endAnimation(mScrimBehind, TAG_KEY_ANIM);
             endAnimation(mScrimInFront, TAG_KEY_ANIM);
+            endAnimation(mScrimForBubble, TAG_KEY_ANIM);
 
             if (!animationFinished[0]) {
                 throw new IllegalStateException("Animation never finished");
@@ -740,6 +859,7 @@
 
         /**
          * Do not wait for a frame since we're in a test environment.
+         *
          * @param callback What to execute.
          */
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 63f653b..3c445c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -37,6 +37,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -87,6 +88,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(StatusBarWindowController.class);
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
         when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
         when(mLockIconContainer.animate()).thenReturn(mock(ViewPropertyAnimator.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 06d76eb..5a6f27d 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
@@ -153,8 +153,6 @@
         StatusBarNotification bubbleSbn = mBubbleNotificationRow.getStatusBarNotification();
         bubbleSbn.getNotification().contentIntent = mContentIntent;
         bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
-        // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
-        mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
 
         mActiveNotifications = new ArrayList<>();
         mActiveNotifications.add(mNotificationRow.getEntry());
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/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index a97832f..4b6ca56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -68,7 +68,7 @@
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
                 mNotificationLockscreenUserManager);
-        mDependency.putComponent(CommandQueue.class, mock(CommandQueue.class));
+        mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
 
         mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
                 mock(NotificationGroupManager.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7fbf183..4eb9a31 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;
@@ -208,7 +207,8 @@
 
         mNotificationInterruptionStateProvider =
                 new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
-                        mDreamManager, mAmbientDisplayConfiguration);
+                        mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
+                        mStatusBarStateController);
         mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
                 mNotificationInterruptionStateProvider);
         mDependency.injectMockDependency(NavigationBarController.class);
@@ -416,10 +416,14 @@
                 .setGroupSummary(true)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_SUMMARY)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = 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));
     }
@@ -437,10 +441,14 @@
                 .setGroupSummary(true)
                 .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
                 .build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = 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));
     }
@@ -454,11 +462,15 @@
         when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = 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));
     }
@@ -472,10 +484,14 @@
         when(mHeadsUpSuppressor.canHeadsUp(any(), any())).thenReturn(true);
 
         Notification n = new Notification.Builder(getContext(), "a").build();
-        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, "a", 0, 0, n,
-                UserHandle.of(0), null, 0);
-        NotificationEntry entry = 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));
     }
@@ -870,8 +886,11 @@
                 Context context,
                 PowerManager powerManager,
                 IDreamManager dreamManager,
-                AmbientDisplayConfiguration ambientDisplayConfiguration) {
-            super(context, powerManager, dreamManager, ambientDisplayConfiguration);
+                AmbientDisplayConfiguration ambientDisplayConfiguration,
+                NotificationFilter filter,
+                StatusBarStateController controller) {
+            super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
+                    controller);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 766ad97..e629a4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -27,7 +27,6 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -78,6 +77,7 @@
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
                 mTestableLooper.getLooper(),
+                mTestableLooper.getLooper(),
                 mMockBluetoothManager);
     }
 
@@ -109,18 +109,13 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
-
         // Trigger the state getting.
         assertEquals(BluetoothDevice.BOND_NONE, mBluetoothControllerImpl.getBondState(device));
 
-        mTestableLooper.processMessages(1);
-        mainLooper.processAllMessages();
+        mTestableLooper.processAllMessages();
 
         assertEquals(BluetoothDevice.BOND_BONDED, mBluetoothControllerImpl.getBondState(device));
         verify(callback).onBluetoothDevicesChanged();
-        mainLooper.destroy();
     }
 
     @Test
@@ -130,20 +125,15 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
-
         // Trigger the state getting.
         assertEquals(BluetoothProfile.STATE_DISCONNECTED,
                 mBluetoothControllerImpl.getMaxConnectionState(device));
 
-        mTestableLooper.processMessages(1);
-        mainLooper.processAllMessages();
+        mTestableLooper.processAllMessages();
 
         assertEquals(BluetoothProfile.STATE_CONNECTED,
                 mBluetoothControllerImpl.getMaxConnectionState(device));
         verify(callback).onBluetoothDevicesChanged();
-        mainLooper.destroy();
     }
 
     @Test
@@ -153,19 +143,11 @@
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+        // Trigger the state getting.
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothControllerImpl.getMaxConnectionState(null));
 
-        try {
-            // Trigger the state getting.
-            assertEquals(BluetoothProfile.STATE_DISCONNECTED,
-                    mBluetoothControllerImpl.getMaxConnectionState(null));
-
-            mTestableLooper.processMessages(1);
-            mainLooper.processAllMessages();
-        } finally {
-            mainLooper.destroy();
-        }
+        mTestableLooper.processAllMessages();
     }
 
     @Test
@@ -217,15 +199,8 @@
         mBluetoothControllerImpl.onAclConnectionStateChanged(device,
                 BluetoothProfile.STATE_CONNECTED);
 
-        // Grab the main looper, we'll need it later.
-        TestableLooper mainLooper = new TestableLooper(Looper.getMainLooper());
+        mTestableLooper.processAllMessages();
 
-        try {
-            mTestableLooper.processAllMessages();
-            mainLooper.processAllMessages();
-        } finally {
-            mainLooper.destroy();
-        }
         assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
         verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 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/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index e691b7d..c03f07e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.app.Instrumentation;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.Network;
@@ -48,6 +49,8 @@
 import android.testing.TestableResources;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.telephony.cdma.EriInfo;
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.settingslib.net.DataUsageController;
@@ -95,6 +98,7 @@
     protected SubscriptionDefaults mMockSubDefaults;
     protected DeviceProvisionedController mMockProvisionController;
     protected DeviceProvisionedListener mUserCallback;
+    protected Instrumentation mInstrumentation;
 
     protected int mSubId;
 
@@ -116,6 +120,7 @@
 
     @Before
     public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         Settings.Global.putInt(mContext.getContentResolver(), Global.AIRPLANE_MODE_ON, 0);
         TestableResources res = mContext.getOrCreateTestableResources();
         res.addOverride(R.string.cell_data_off_content_description, NO_DATA_STRING);
@@ -240,6 +245,16 @@
         NetworkControllerImpl.Config.add5GIconMapping("not_restricted_rrc_idle:5g", mConfig);
     }
 
+    public void setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds() {
+        final int enableDisplayGraceTimeSec = 30;
+        NetworkControllerImpl.Config.setDisplayGraceTime(enableDisplayGraceTimeSec, mConfig);
+    }
+
+    public void setupDefaultNr5GIconDisplayGracePeriodTime_disabled() {
+        final int disableDisplayGraceTimeSec = 0;
+        NetworkControllerImpl.Config.setDisplayGraceTime(disableDisplayGraceTimeSec, mConfig);
+    }
+
     public void setConnectivityViaBroadcast(
         int networkType, boolean validated, boolean isConnected) {
         setConnectivityCommon(networkType, validated, isConnected);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index f0394da..aa4723a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,7 @@
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -145,7 +147,7 @@
     }
 
     @Test
-    public void testNoInternetIcon_withoutDefaultSub() {
+    public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
@@ -156,11 +158,11 @@
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0,
                 true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false,
-                false, true, NOT_DEFAULT_DATA_STRING);
+                false, false, NOT_DEFAULT_DATA_STRING);
     }
 
     @Test
-    public void testDataDisabledIcon_withoutDefaultSub() {
+    public void testNonDefaultSIM_showsFullSignal_disconnected() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
         setupDefaultSignal();
@@ -171,7 +173,7 @@
         // Verify that a SignalDrawable with a cut out is used to display data disabled.
         verifyLastMobileDataIndicators(true, DEFAULT_SIGNAL_STRENGTH, 0,
                 true, DEFAULT_QS_SIGNAL_STRENGTH, 0, false,
-                false, true, NOT_DEFAULT_DATA_STRING);
+                false, false, NOT_DEFAULT_DATA_STRING);
     }
 
     @Test
@@ -245,6 +247,186 @@
     }
 
     @Test
+    public void testNr5GIcon_displayGracePeriodTime_enabled() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        // While nrIconDisplayGracePeriodMs > 0 & is Nr5G, mIsShowingIconGracefully should be true
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs > 0);
+        assertTrue(mMobileSignalController.mIsShowingIconGracefully);
+    }
+
+    @Test
+    public void testNr5GIcon_displayGracePeriodTime_disabled() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        setupDefaultSignal();
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs == 0);
+
+        // While nrIconDisplayGracePeriodMs <= 0, mIsShowingIconGracefully should be false
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        assertFalse(mMobileSignalController.mIsShowingIconGracefully);
+    }
+
+    @Test
+    public void testNr5GIcon_enableDisplayGracePeriodTime_showIconGracefully() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Enabled timer Nr5G switch to None Nr5G, showing 5G icon gracefully
+        ServiceState ssLte = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(ssLte).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(ssLte).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ssLte);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+    }
+
+    @Test
+    public void testNr5GIcon_disableDisplayGracePeriodTime_showLatestIconImmediately() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_LTE);
+    }
+
+    @Test
+    public void testNr5GIcon_resetDisplayGracePeriodTime_whenDataDisconnected() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Disabled timer, when out of service, reset timer to display latest state
+        updateDataConnectionState(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(mServiceState).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_DISCONNECTED,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+
+        verifyDataIndicators(0);
+    }
+
+    @Test
+    public void testNr5GIcon_enableDisplayGracePeriodTime_show5G_switching_5GPlus() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss5G = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5G).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss5G).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5G);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // When timeout enabled, 5G/5G+ switching should be updated immediately
+        ServiceState ss5GPlus = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5GPlus).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(ss5GPlus).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5GPlus);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G_PLUS);
+    }
+
+    @Test
+    public void testNr5GIcon_carrierDisabledDisplayGracePeriodTime_shouldUpdateIconImmediately() {
+        setupDefaultNr5GIconConfiguration();
+        setupDefaultNr5GIconDisplayGracePeriodTime_enableThirtySeconds();
+        setupDefaultSignal();
+        mNetworkController.handleConfigurationChanged();
+        mPhoneStateListener.onServiceStateChanged(mServiceState);
+
+        ServiceState ss5G = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(ss5G).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(ss5G).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ss5G);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // State from NR_5G to NONE NR_5G with timeout, should show previous 5G icon
+        ServiceState ssLte = Mockito.mock(ServiceState.class);
+        doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(ssLte).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_UNKNOWN).when(ssLte).getNrFrequencyRange();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mPhoneStateListener.onServiceStateChanged(ssLte);
+
+        verifyDataIndicators(TelephonyIcons.ICON_5G);
+
+        // Update nrIconDisplayGracePeriodMs to 0
+        setupDefaultNr5GIconDisplayGracePeriodTime_disabled();
+        mNetworkController.handleConfigurationChanged();
+
+        // State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
+        doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+        mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        assertTrue(mConfig.nrIconDisplayGracePeriodMs == 0);
+        verifyDataIndicators(TelephonyIcons.ICON_LTE);
+    }
+
+    @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
         when(mMockTm.isDataCapable()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 6e3d906..3451183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -1,5 +1,12 @@
 package com.android.systemui.statusbar.policy;
 
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.content.Intent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
@@ -16,13 +23,6 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index b59bac1..0d56cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -23,7 +23,6 @@
 import static junit.framework.Assert.fail;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,7 +36,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -48,10 +46,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.statusbar.NotificationEntryBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -65,6 +65,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
@@ -112,6 +113,7 @@
         mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
             action.onDismiss();
         });
+        mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(ShadeController.class);
         mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
         mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
@@ -137,10 +139,10 @@
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text").build();
-        StatusBarNotification sbn = mock(StatusBarNotification.class);
-        when(sbn.getNotification()).thenReturn(mNotification);
-        when(sbn.getKey()).thenReturn(TEST_NOTIFICATION_KEY);
-        mEntry = NotificationEntry.buildForTest(sbn);
+
+        mEntry = new NotificationEntryBuilder()
+                .setNotification(mNotification)
+                .build();
 
         mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
     }
@@ -468,7 +470,11 @@
                 new Intent(TEST_ACTION), 0);
         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
         SmartReplyView.SmartReplies smartReplies =
-                new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant);
+                new SmartReplyView.SmartReplies(
+                        Arrays.asList(choices),
+                        input,
+                        pendingIntent,
+                        fromAssistant);
         return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
                 useDelayedOnClickListener);
     }
@@ -494,6 +500,7 @@
     private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
         mView.resetSmartSuggestions(mContainer);
         List<Button> actions = mView.inflateSmartActions(
+                getContext(),
                 new SmartReplyView.SmartActions(createActions(actionTitles), false),
                 mLogger,
                 mEntry,
@@ -514,6 +521,7 @@
         List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
                 useDelayedOnClickListener);
         smartSuggestions.addAll(mView.inflateSmartActions(
+                getContext(),
                 new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
                 mLogger,
                 mEntry,
@@ -553,7 +561,7 @@
         // Add smart replies
         Button previous = null;
         SmartReplyView.SmartReplies smartReplies =
-                new SmartReplyView.SmartReplies(choices, null, null, false);
+                new SmartReplyView.SmartReplies(Arrays.asList(choices), null, null, false);
         for (int i = 0; i < choices.length; ++i) {
             Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
                     null /* SmartReplyController */, null /* NotificationEntry */,
@@ -860,7 +868,7 @@
     }
 
     private Button inflateActionButton(Notification.Action action) {
-        return SmartReplyView.inflateActionButton(mView, getContext(), 0,
+        return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0,
                 new SmartReplyView.SmartActions(Collections.singletonList(action), false),
                 mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
similarity index 71%
rename from packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 4a9b1b3..9149599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 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.
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util;
+package com.android.systemui.util.sensors;
 
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -24,15 +23,15 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
+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 com.android.systemui.utils.hardware.FakeSensorManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,20 +39,21 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
 public class AsyncSensorManagerTest extends SysuiTestCase {
 
-    private TestableAsyncSensorManager mAsyncSensorManager;
-    private FakeSensorManager mFakeSensorManager;
+    private AsyncSensorManager mAsyncSensorManager;
     private SensorEventListener mListener;
-    private FakeSensorManager.MockProximitySensor mSensor;
+    private FakeSensorManager.FakeProximitySensor 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();
+        FakeSensorManager fakeSensorManager = new FakeSensorManager(mContext);
+        mAsyncSensorManager = new AsyncSensorManager(
+                fakeSensorManager, mPluginManager, new Handler());
+        mSensor = fakeSensorManager.getFakeProximitySensor();
         mListener = mock(SensorEventListener.class);
     }
 
@@ -61,7 +61,7 @@
     public void registerListenerImpl() throws Exception {
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was registered.
         mSensor.sendProximityResult(true);
@@ -73,7 +73,7 @@
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
         mAsyncSensorManager.unregisterListener(mListener);
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was unregistered.
         mSensor.sendProximityResult(true);
@@ -85,7 +85,7 @@
         mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
         mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
 
-        mAsyncSensorManager.waitUntilRequestsCompleted();
+        waitUntilRequestsCompleted();
 
         // Verify listener was unregistered.
         mSensor.sendProximityResult(true);
@@ -98,13 +98,7 @@
                 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));
-        }
+    public void waitUntilRequestsCompleted() {
+        TestableLooper.get(this).processAllMessages();
     }
-}
\ No newline at end of file
+}
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/utils/hardware/FakeSensorManager.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 29b8ab60..1deb495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/hardware/FakeSensorManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
-package com.android.systemui.utils.hardware;
+package com.android.systemui.util.sensors;
 
 import android.content.Context;
 import android.hardware.HardwareBuffer;
@@ -54,7 +54,7 @@
 
     public static final String TAP_SENSOR_TYPE = "tapSensorType";
 
-    private final MockProximitySensor mMockProximitySensor;
+    private final FakeProximitySensor mFakeProximitySensor;
     private final FakeGenericSensor mFakeLightSensor;
     private final FakeGenericSensor mFakeTapSensor;
     private final FakeGenericSensor[] mSensors;
@@ -68,14 +68,14 @@
         }
 
         mSensors = new FakeGenericSensor[]{
-                mMockProximitySensor = new MockProximitySensor(proxSensor),
+                mFakeProximitySensor = new FakeProximitySensor(proxSensor),
                 mFakeLightSensor = new FakeGenericSensor(createSensor(Sensor.TYPE_LIGHT, null)),
                 mFakeTapSensor = new FakeGenericSensor(createSensor(99, TAP_SENSOR_TYPE))
         };
     }
 
-    public MockProximitySensor getMockProximitySensor() {
-        return mMockProximitySensor;
+    public FakeProximitySensor getFakeProximitySensor() {
+        return mFakeProximitySensor;
     }
 
     public FakeGenericSensor getFakeLightSensor() {
@@ -231,9 +231,9 @@
         setter.invoke(sensor, type);
     }
 
-    public class MockProximitySensor extends FakeGenericSensor {
+    public class FakeProximitySensor extends FakeGenericSensor {
 
-        private MockProximitySensor(Sensor sensor) {
+        private FakeProximitySensor(Sensor sensor) {
             super(sensor);
         }
 
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/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
index 95c7a4d..2fb0e0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -80,9 +80,4 @@
     public long calculateGoingToFullShadeDelay() {
         return 0;
     }
-
-    @Override
-    public boolean canSkipBouncer() {
-        return false;
-    }
 }
diff --git a/packages/WAPPushManager/Android.bp b/packages/WAPPushManager/Android.bp
index 1bec492..c391369 100644
--- a/packages/WAPPushManager/Android.bp
+++ b/packages/WAPPushManager/Android.bp
@@ -9,4 +9,6 @@
     optimize: {
         proguard_flags_files: ["proguard.flags"],
     },
+
+    product_specific: true,
 }
diff --git a/packages/WAPPushManager/CleanSpec.mk b/packages/WAPPushManager/CleanSpec.mk
index b84e1b6..2dcbb10 100644
--- a/packages/WAPPushManager/CleanSpec.mk
+++ b/packages/WAPPushManager/CleanSpec.mk
@@ -47,3 +47,5 @@
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
+
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/WAPPushManager)
diff --git a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
index dc77981..0ff85fe 100644
--- a/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationCornerOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"Сделать вырез в углу"</string>
+    <string name="display_cutout_emulation_overlay" msgid="1677693377327336341">"В правом углу"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
index a02eaf7..2493da3 100644
--- a/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationDoubleOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Увеличить вырез вдвое"</string>
+    <string name="display_cutout_emulation_overlay" msgid="5323179900047630217">"Сверху и снизу"</string>
 </resources>
diff --git a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
index 1d1656d..89ac1c3 100644
--- a/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationTallOverlay/res/values-ru/strings.xml
@@ -17,5 +17,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сделать вырез выше"</string>
+    <string name="display_cutout_emulation_overlay" msgid="6424539415439220018">"Сверху"</string>
 </resources>
diff --git a/proto/src/gnss.proto b/proto/src/gnss.proto
index 1509fc0..33dbb26 100644
--- a/proto/src/gnss.proto
+++ b/proto/src/gnss.proto
@@ -48,6 +48,27 @@
 
   // Hardware revision (EVT, DVT, PVT etc.)
   optional string hardware_revision = 13;
+
+  // Total number of sv status messages processed
+  optional int32 num_sv_status_processed = 14;
+
+  // Total number of L5 sv status messages processed
+  optional int32 num_l5_sv_status_processed = 15;
+
+  // Total number of sv status messages processed, where sv is used in fix
+  optional int32 num_sv_status_used_in_fix = 16;
+
+  // Total number of L5 sv status messages processed, where sv is used in fix
+  optional int32 num_l5_sv_status_used_in_fix = 17;
+
+  // Number of l5 top 4 average CN0 processed
+  optional int32 num_l5_top_four_average_cn0_processed = 18;
+
+  // Mean of l5 top 4 average CN0 (dB-Hz)
+  optional double mean_l5_top_four_average_cn0_db_hz = 19;
+
+  // Standard deviation of l5 top 4 average CN0 (dB-Hz)
+  optional double standard_deviation_l5_top_four_average_cn0_db_hz = 20;
 }
 
 // Power metrics
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 5a4892c..5b826b1 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7414,6 +7414,17 @@
     // Note: Gear icon is shown next to gesture navigation preference and opens sensitivity dialog
     SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG = 1748;
 
+
+
+    // ACTION: Chooser > User taps a system-provided target such as copy
+    //   SUBTYPE: Index of target
+    // CATEGORY: GLOBAL_SYSTEM_UI
+    // OS: Q - QPR1
+    ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET = 1749;
+
+    // OPEN: Settings > System > Aware > Aware Display
+    // OS: Q
+    SETTINGS_AWARE_DISPLAY = 1750;
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 8ad2489..fbf6ca5 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -1792,6 +1792,25 @@
 
   // Indicates if we are logging LinkSpeedCount in metrics
   optional bool link_speed_counts_logging_enabled = 4;
+
+  // Duration for evaluating Wifi condition to trigger a data stall
+  // measured in milliseconds
+  optional int32 data_stall_duration_ms = 5;
+
+  // Threshold of Tx throughput below which to trigger a data stall
+  // measured in Kbps
+  optional int32 data_stall_tx_tput_thr_kbps = 6;
+
+  // Threshold of Rx throughput below which to trigger a data stall
+  // measured in Kbps
+  optional int32 data_stall_rx_tput_thr_kbps = 7;
+
+  // Threshold of Tx packet error rate above which to trigger a data stall
+  // in percentage
+  optional int32 data_stall_tx_per_thr = 8;
+
+  // Threshold of CCA level above which to trigger a data stall in percentage
+  optional int32 data_stall_cca_level_thr = 9;
 }
 
 message WifiIsUnusableEvent {
diff --git a/services/Android.bp b/services/Android.bp
index 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 1d936f2..4f021ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -23,7 +23,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
@@ -352,7 +352,7 @@
 
     @Override
     public List<AccessibilityWindowInfo> getWindows() {
-        ensureWindowsAvailableTimed();
+        ensureWindowsAvailableTimed(Display.DEFAULT_DISPLAY);
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -362,8 +362,6 @@
             if (!permissionGranted) {
                 return null;
             }
-            // TODO [Multi-Display] (b/134891479) :
-            // using correct display Id to replace DEFAULT_DISPLAY.
             List<AccessibilityWindowInfo> internalWindowList =
                     mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
             if (internalWindowList == null) {
@@ -387,7 +385,14 @@
 
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
-        ensureWindowsAvailableTimed();
+        int displayId = Display.INVALID_DISPLAY;
+        synchronized (mLock) {
+            if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
+                        mSystemSupport.getCurrentUserIdLocked(), windowId);
+            }
+        }
+        ensureWindowsAvailableTimed(displayId);
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
                 return null;
@@ -1168,9 +1173,9 @@
         }
     }
 
-    public void notifyGesture(AccessibilityGestureInfo gestureInfo) {
+    public void notifyGesture(AccessibilityGestureEvent gestureEvent) {
         mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
-                gestureInfo).sendToTarget();
+                gestureEvent).sendToTarget();
     }
 
     public void notifyClearAccessibilityNodeInfoCache() {
@@ -1259,7 +1264,7 @@
         }
     }
 
-    private void notifyGestureInternal(AccessibilityGestureInfo gestureInfo) {
+    private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
         final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
         if (listener != null) {
             try {
@@ -1308,28 +1313,28 @@
 
     /**
      * Request that the system make sure windows are available to interrogate.
+     *
+     * @param displayId The logical display id.
      */
-    private void ensureWindowsAvailableTimed() {
+    private void ensureWindowsAvailableTimed(int displayId) {
         synchronized (mLock) {
-            // TODO [Multi-Display] (b/134891479) :
-            // using correct display Id to replace DEFAULT_DISPLAY.
-            if (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) != null) {
+            if (mA11yWindowManager.getWindowListLocked(displayId) != null) {
                 return;
             }
             // If we have no registered callback, update the state we
             // we may have to register one but it didn't happen yet.
-            if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+            if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                 // Invokes client change to make sure tracking window enabled.
                 mSystemSupport.onClientChangeLocked(false);
             }
             // We have no windows but do not care about them, done.
-            if (!mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+            if (!mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                 return;
             }
 
             // Wait for the windows with a timeout.
             final long startMillis = SystemClock.uptimeMillis();
-            while (mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY) == null) {
+            while (mA11yWindowManager.getWindowListLocked(displayId) == null) {
                 final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
                 final long remainMillis = WAIT_WINDOWS_TIMEOUT_MILLIS - elapsedMillis;
                 if (remainMillis <= 0) {
@@ -1464,7 +1469,7 @@
             final int type = message.what;
             switch (type) {
                 case MSG_ON_GESTURE: {
-                    notifyGestureInternal((AccessibilityGestureInfo) message.obj);
+                    notifyGestureInternal((AccessibilityGestureEvent) message.obj);
                 } break;
 
                 case MSG_CLEAR_ACCESSIBILITY_CACHE: {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ddf5bbe..1f11059 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -27,7 +27,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -580,17 +580,24 @@
             // Make sure clients receiving this event will be able to get the
             // current state of the windows as the window manager may be delaying
             // the computation for performance reasons.
-            // TODO [Multi-Display] : using correct display Id to replace DEFAULT_DISPLAY.
             boolean shouldComputeWindows = false;
+            int displayId = Display.INVALID_DISPLAY;
             synchronized (mLock) {
+                final int windowId = event.getWindowId();
                 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
-                        && mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) {
+                        && windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) {
+                    displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(
+                            mCurrentUserId, windowId);
+                }
+                if (displayId != Display.INVALID_DISPLAY
+                        && mA11yWindowManager.isTrackingWindowsLocked(displayId)) {
                     shouldComputeWindows = true;
                 }
             }
             if (shouldComputeWindows) {
-                WindowManagerInternal wm = LocalServices.getService(WindowManagerInternal.class);
-                wm.computeWindowsForAccessibility(Display.DEFAULT_DISPLAY);
+                final WindowManagerInternal wm = LocalServices.getService(
+                        WindowManagerInternal.class);
+                wm.computeWindowsForAccessibility(displayId);
             }
             synchronized (mLock) {
                 notifyAccessibilityServicesDelayedLocked(event, false);
@@ -821,12 +828,17 @@
         }
     }
 
-
-    public boolean onGesture(AccessibilityGestureInfo gestureInfo) {
+    /**
+     * Called when a gesture is detected on a display.
+     *
+     * @param gestureEvent the detail of the gesture.
+     * @return true if the event is handled.
+     */
+    public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
         synchronized (mLock) {
-            boolean handled = notifyGestureLocked(gestureInfo, false);
+            boolean handled = notifyGestureLocked(gestureEvent, false);
             if (!handled) {
-                handled = notifyGestureLocked(gestureInfo, true);
+                handled = notifyGestureLocked(gestureEvent, true);
             }
             return handled;
         }
@@ -1021,7 +1033,7 @@
         }
     }
 
-    private boolean notifyGestureLocked(AccessibilityGestureInfo gestureInfo, boolean isDefault) {
+    private boolean notifyGestureLocked(AccessibilityGestureEvent gestureEvent, boolean isDefault) {
         // TODO: Now we are giving the gestures to the last enabled
         //       service that can handle them which is the last one
         //       in our list since we write the last enabled as the
@@ -1035,7 +1047,7 @@
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
             if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
-                service.notifyGesture(gestureInfo);
+                service.notifyGesture(gestureEvent);
                 return true;
             }
         }
@@ -2193,6 +2205,9 @@
         }
         synchronized(mLock) {
             final UserState userState = getUserStateLocked(mCurrentUserId);
+            if (userState.mServiceToEnableWithShortcut == null) {
+                return null;
+            }
             return userState.mServiceToEnableWithShortcut.flattenToString();
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 82a593c..0038a27 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -1463,6 +1463,20 @@
     }
 
     /**
+     * Returns the display ID according to given userId and windowId.
+     *
+     * @param userId The userId
+     * @param windowId The windowId
+     * @return The display ID
+     */
+    public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
+        final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
+        final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+
+        return displayId;
+    }
+
+    /**
      * Gets current input focused window token from window manager, and returns its windowId.
      *
      * @param userId The userId
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 672518c..b9b2654 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -24,10 +24,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -191,7 +188,7 @@
         ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
                 ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
         screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                true, true, new Handler(Looper.getMainLooper()));
+                true, true, new Handler(Looper.getMainLooper()), null);
         return true;
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
index 9101a01..7e8fb29 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/AccessibilityGestureDetector.java
@@ -16,7 +16,7 @@
 
 package com.android.server.accessibility.gestures;
 
-import android.accessibilityservice.AccessibilityGestureInfo;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.content.Context;
 import android.gesture.GesturePoint;
@@ -119,11 +119,11 @@
         /**
          * Called when an event stream is recognized as a gesture.
          *
-         * @param gestureInfo Information about the gesture.
+         * @param gestureEvent Information about the gesture.
          *
          * @return true if the event is consumed, else false
          */
-        boolean onGestureCompleted(AccessibilityGestureInfo gestureInfo);
+        boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent);
 
         /**
          * Called when the system has decided an event stream doesn't match any
@@ -567,19 +567,19 @@
             switch (direction) {
                 case LEFT:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_LEFT,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_LEFT,
                                     displayId));
                 case RIGHT:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_RIGHT,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_RIGHT,
                                     displayId));
                 case UP:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_UP,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_UP,
                                     displayId));
                 case DOWN:
                     return mListener.onGestureCompleted(
-                            new AccessibilityGestureInfo(AccessibilityService.GESTURE_SWIPE_DOWN,
+                            new AccessibilityGestureEvent(AccessibilityService.GESTURE_SWIPE_DOWN,
                                     displayId));
                 default:
                     // Do nothing.
@@ -600,7 +600,7 @@
             int segmentDirection1 = toDirection(dX1, dY1);
             int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
             return mListener.onGestureCompleted(
-                    new AccessibilityGestureInfo(gestureId, displayId));
+                    new AccessibilityGestureEvent(gestureId, displayId));
         }
         // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
         return mListener.onGestureCancelled(event, policyFlags);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
new file mode 100644
index 0000000..dc7a9aa
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.gestures;
+
+import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
+import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
+import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accessibility.EventStreamTransformation;
+import com.android.server.policy.WindowManagerPolicy;
+
+/**
+ * This class dispatches motion events and accessibility events relating to touch exploration and
+ * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is
+ * set correctly so that events go to the right place.
+ */
+class EventDispatcher {
+    private static final String LOG_TAG = "EventDispatcher";
+
+    private final AccessibilityManagerService mAms;
+    private Context mContext;
+    // The receiver of motion events.
+    private EventStreamTransformation mReceiver;
+    // Keep track of which pointers sent to the system are down.
+    private int mInjectedPointersDown;
+
+    // The time of the last injected down.
+    private long mLastInjectedDownEventTime;
+
+    // The last injected hover event.
+    private MotionEvent mLastInjectedHoverEvent;
+    private TouchState mState;
+
+    EventDispatcher(
+            Context context,
+            AccessibilityManagerService ams,
+            EventStreamTransformation receiver,
+            TouchState state) {
+        mContext = context;
+        mAms = ams;
+        mReceiver = receiver;
+        mState = state;
+    }
+
+    public void setReceiver(EventStreamTransformation receiver) {
+        mReceiver = receiver;
+    }
+
+    /**
+     * Sends an event.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param action The action of the event.
+     * @param pointerIdBits The bits of the pointers to send.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, int policyFlags) {
+        prototype.setAction(action);
+
+        MotionEvent event = null;
+        if (pointerIdBits == ALL_POINTER_ID_BITS) {
+            event = prototype;
+        } else {
+            try {
+                event = prototype.split(pointerIdBits);
+            } catch (IllegalArgumentException e) {
+                Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
+                return;
+            }
+        }
+        if (action == MotionEvent.ACTION_DOWN) {
+            event.setDownTime(event.getEventTime());
+        } else {
+            event.setDownTime(getLastInjectedDownEventTime());
+        }
+        if (DEBUG) {
+            Slog.d(
+                    LOG_TAG,
+                    "Injecting event: "
+                            + event
+                            + ", policyFlags=0x"
+                            + Integer.toHexString(policyFlags));
+        }
+
+        // Make sure that the user will see the event.
+        policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+        // TODO: For now pass null for the raw event since the touch
+        //       explorer is the last event transformation and it does
+        //       not care about the raw event.
+        if (mReceiver != null) {
+            mReceiver.onMotionEvent(event, null, policyFlags);
+        } else {
+            Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
+        }
+        updateState(event);
+
+        if (event != prototype) {
+            event.recycle();
+        }
+    }
+
+    /**
+     * Sends an accessibility event of the given type.
+     *
+     * @param type The event type.
+     */
+    void sendAccessibilityEvent(int type) {
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
+        if (accessibilityManager.isEnabled()) {
+            AccessibilityEvent event = AccessibilityEvent.obtain(type);
+            event.setWindowId(mAms.getActiveWindowId());
+            accessibilityManager.sendAccessibilityEvent(event);
+            if (DEBUG) {
+                Slog.d(
+                        LOG_TAG,
+                        "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
+            }
+        }
+        // Todo: get rid of this and have TouchState control the sending of events rather than react
+        // to it.
+        mState.onInjectedAccessibilityEvent(type);
+    }
+
+    /**
+     * Processes an injected {@link MotionEvent} event.
+     *
+     * @param event The event to process.
+     */
+    void updateState(MotionEvent event) {
+        final int action = event.getActionMasked();
+        final int pointerId = event.getPointerId(event.getActionIndex());
+        final int pointerFlag = (1 << pointerId);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                mInjectedPointersDown |= pointerFlag;
+                mLastInjectedDownEventTime = event.getDownTime();
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP:
+                mInjectedPointersDown &= ~pointerFlag;
+                if (mInjectedPointersDown == 0) {
+                    mLastInjectedDownEventTime = 0;
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_ENTER:
+            case MotionEvent.ACTION_HOVER_MOVE:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mLastInjectedHoverEvent != null) {
+                    mLastInjectedHoverEvent.recycle();
+                }
+                mLastInjectedHoverEvent = MotionEvent.obtain(event);
+                break;
+        }
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
+        }
+    }
+
+    /** Clears the internals state. */
+    public void clear() {
+        mInjectedPointersDown = 0;
+    }
+
+    /** @return The time of the last injected down event. */
+    public long getLastInjectedDownEventTime() {
+        return mLastInjectedDownEventTime;
+    }
+
+    /** @return The number of down pointers injected to the view hierarchy. */
+    public int getInjectedPointerDownCount() {
+        return Integer.bitCount(mInjectedPointersDown);
+    }
+
+    /** @return The bits of the injected pointers that are down. */
+    public int getInjectedPointersDown() {
+        return mInjectedPointersDown;
+    }
+
+    /**
+     * Whether an injected pointer is down.
+     *
+     * @param pointerId The unique pointer id.
+     * @return True if the pointer is down.
+     */
+    public boolean isInjectedPointerDown(int pointerId) {
+        final int pointerFlag = (1 << pointerId);
+        return (mInjectedPointersDown & pointerFlag) != 0;
+    }
+
+    /** @return The the last injected hover event. */
+    public MotionEvent getLastInjectedHoverEvent() {
+        return mLastInjectedHoverEvent;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("=========================");
+        builder.append("\nDown pointers #");
+        builder.append(Integer.bitCount(mInjectedPointersDown));
+        builder.append(" [ ");
+        for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+            if ((mInjectedPointersDown & i) != 0) {
+                builder.append(i);
+                builder.append(" ");
+            }
+        }
+        builder.append("]");
+        builder.append("\n=========================");
+        return builder.toString();
+    }
+
+    /**
+     * Computes the action for an injected event based on a masked action and a pointer index.
+     *
+     * @param actionMasked The masked action.
+     * @param pointerIndex The index of the pointer which has changed.
+     * @return The action to be used for injection.
+     */
+    private int computeInjectionAction(int actionMasked, int pointerIndex) {
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_POINTER_DOWN:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 0) {
+                    return MotionEvent.ACTION_DOWN;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_DOWN;
+                }
+            case MotionEvent.ACTION_POINTER_UP:
+                // Compute the action based on how many down pointers are injected.
+                if (getInjectedPointerDownCount() == 1) {
+                    return MotionEvent.ACTION_UP;
+                } else {
+                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
+                            | MotionEvent.ACTION_POINTER_UP;
+                }
+            default:
+                return actionMasked;
+        }
+    }
+    /**
+     * Sends down events to the view hierarchy for all pointers which are not already being
+     * delivered i.e. pointers that are not yet injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
+
+        // Inject the injected pointers.
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Do not send event for already delivered pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                pointerIdBits |= (1 << pointerId);
+                final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
+                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+            }
+        }
+    }
+
+    /**
+     * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
+     * pointers that are injected.
+     *
+     * @param prototype The prototype from which to create the injected events.
+     * @param policyFlags The policy flags associated with the event.
+     */
+    void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
+        int pointerIdBits = 0;
+        final int pointerCount = prototype.getPointerCount();
+        for (int i = 0; i < pointerCount; i++) {
+            final int pointerId = prototype.getPointerId(i);
+            // Skip non injected down pointers.
+            if (!isInjectedPointerDown(pointerId)) {
+                continue;
+            }
+            pointerIdBits |= (1 << pointerId);
+            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
+            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
+        }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 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/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/TransportManager.java
rename to services/backup/backuplib/java/com/android/server/backup/TransportManager.java
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportClient.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportClient.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportClientManager.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportClientManager.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportNotAvailableException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportNotRegisteredException.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportNotRegisteredException.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportStats.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportStats.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportStats.java
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
similarity index 100%
rename from services/backup/java/com/android/server/backup/transport/TransportUtils.java
rename to services/backup/backuplib/java/com/android/server/backup/transport/TransportUtils.java
diff --git a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java b/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
deleted file mode 100644
index 5bec1a9..0000000
--- a/services/backup/java/com/android/server/backup/encryption/chunk/Chunk.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.android.server.backup.encryption.chunk;
-
-import android.util.proto.ProtoInputStream;
-
-import java.io.IOException;
-
-/**
- * Information about a chunk entry in a protobuf. Only used for reading from a {@link
- * ProtoInputStream}.
- */
-public class Chunk {
-    /**
-     * Reads a Chunk from a {@link ProtoInputStream}. Expects the message to be of format {@link
-     * ChunksMetadataProto.Chunk}.
-     *
-     * @param inputStream currently at a {@link ChunksMetadataProto.Chunk} message.
-     * @throws IOException when the message is not structured as expected or a field can not be
-     *     read.
-     */
-    static Chunk readFromProto(ProtoInputStream inputStream) throws IOException {
-        Chunk result = new Chunk();
-
-        while (inputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-            switch (inputStream.getFieldNumber()) {
-                case (int) ChunksMetadataProto.Chunk.HASH:
-                    result.mHash = inputStream.readBytes(ChunksMetadataProto.Chunk.HASH);
-                    break;
-                case (int) ChunksMetadataProto.Chunk.LENGTH:
-                    result.mLength = inputStream.readInt(ChunksMetadataProto.Chunk.LENGTH);
-                    break;
-            }
-        }
-
-        return result;
-    }
-
-    private int mLength;
-    private byte[] mHash;
-
-    /** Private constructor. This class should only be instantiated by calling readFromProto. */
-    private Chunk() {
-        // Set default values for fields in case they are not available in the proto.
-        mHash = new byte[]{};
-        mLength = 0;
-    }
-
-    public int getLength() {
-        return mLength;
-    }
-
-    public byte[] getHash() {
-        return mHash;
-    }
-}
\ No newline at end of file
diff --git a/services/backup/java/com/android/server/backup/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/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 2a2dc3d..18a8148 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2165,7 +2165,11 @@
         }
     }
 
-    void systemReady() {
+    /**
+     * Called when the system is ready and ConnectivityService can initialize remaining components.
+     */
+    @VisibleForTesting
+    public void systemReady() {
         mProxyTracker.loadGlobalProxy();
         registerNetdEventCallback();
         mTethering.systemReady();
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 5089ee0..69f226f6 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -21,9 +21,7 @@
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
 import static android.location.LocationManager.PASSIVE_PROVIDER;
-import static android.location.LocationProvider.AVAILABLE;
 import static android.os.PowerManager.locationPowerSaveModeToString;
-import static android.provider.Settings.Global.LOCATION_DISABLE_STATUS_CALLBACKS;
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
@@ -1088,34 +1086,6 @@
             pw.decreaseIndent();
         }
 
-        @GuardedBy("mLock")
-        public long getStatusUpdateTimeLocked() {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    return mProvider.getStatusUpdateTime();
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            } else {
-                return 0;
-            }
-        }
-
-        @GuardedBy("mLock")
-        public int getStatusLocked(Bundle extras) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    return mProvider.getStatus(extras);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            } else {
-                return AVAILABLE;
-            }
-        }
-
         @Override
         public void onReportLocation(Location location) {
             synchronized (mLock) {
@@ -1324,18 +1294,6 @@
             super.setRequest(request, workSource);
             mCurrentRequest = request;
         }
-
-        @GuardedBy("mLock")
-        public void setStatusLocked(int status, Bundle extras, long updateTime) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    ((MockProvider) mProvider).setStatus(status, extras, updateTime);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
     }
 
     /**
@@ -1516,34 +1474,6 @@
             throw new IllegalStateException("Request for non-existent listener");
         }
 
-        public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
-            if (mListener != null) {
-                try {
-                    mListener.onStatusChanged(provider, status, extras);
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (RemoteException e) {
-                    return false;
-                }
-            } else {
-                Intent statusChanged = new Intent();
-                statusChanged.putExtras(new Bundle(extras));
-                statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
-                try {
-                    mPendingIntent.send(mContext, 0, statusChanged, this, mHandler,
-                            getResolutionPermission(mAllowedResolutionLevel),
-                            PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
-                    // call this after broadcasting so we do not increment
-                    // if we throw an exception.
-                    incrementPendingBroadcastsLocked();
-                } catch (PendingIntent.CanceledException e) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
         public boolean callLocationChangedLocked(Location location) {
             if (mListener != null) {
                 try {
@@ -2296,7 +2226,6 @@
         private final Receiver mReceiver;
         private boolean mIsForegroundUid;
         private Location mLastFixBroadcast;
-        private long mLastStatusBroadcast;
         private Throwable mStackTrace;  // for debugging only
 
         /**
@@ -3406,26 +3335,6 @@
                 }
             }
 
-            // TODO: location provider status callbacks have been disabled and deprecated, and are
-            // guarded behind this setting now. should be removed completely post-Q
-            if (Settings.Global.getInt(mContext.getContentResolver(),
-                    LOCATION_DISABLE_STATUS_CALLBACKS, 1) == 0) {
-                long newStatusUpdateTime = provider.getStatusUpdateTimeLocked();
-                Bundle extras = new Bundle();
-                int status = provider.getStatusLocked(extras);
-
-                long prevStatusUpdateTime = r.mLastStatusBroadcast;
-                if ((newStatusUpdateTime > prevStatusUpdateTime)
-                        && (prevStatusUpdateTime != 0 || status != AVAILABLE)) {
-
-                    r.mLastStatusBroadcast = newStatusUpdateTime;
-                    if (!receiver.callStatusChangedLocked(provider.getName(), status, extras)) {
-                        receiverDead = true;
-                        Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
-                    }
-                }
-            }
-
             // track expired records
             if (r.mRealRequest.getNumUpdates() <= 0 || r.mRealRequest.getExpireAt() < now) {
                 if (deadUpdateRecords == null) {
@@ -3625,23 +3534,6 @@
     }
 
     @Override
-    public void setTestProviderStatus(String providerName, int status, Bundle extras,
-            long updateTime, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
-        }
-
-        synchronized (mLock) {
-            LocationProvider testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            ((MockLocationProvider) testProvider).setStatusLocked(status, extras, updateTime);
-        }
-    }
-
-    @Override
     @NonNull
     public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
             String opPackageName) {
diff --git a/services/core/java/com/android/server/MountServiceIdler.java b/services/core/java/com/android/server/MountServiceIdler.java
index 1891ba9..6bc1a57 100644
--- a/services/core/java/com/android/server/MountServiceIdler.java
+++ b/services/core/java/com/android/server/MountServiceIdler.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.util.Slog;
+import java.util.concurrent.TimeUnit;
 
 public class MountServiceIdler extends JobService {
     private static final String TAG = "MountServiceIdler";
@@ -48,7 +49,7 @@
                     mStarted = false;
                 }
             }
-            // ... and try again tomorrow
+            // ... and try again right away or tomorrow
             scheduleIdlePass(MountServiceIdler.this);
         }
     };
@@ -98,24 +99,32 @@
     public static void scheduleIdlePass(Context context) {
         JobScheduler tm = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
 
-        Calendar calendar = tomorrowMidnight();
-        final long timeToMidnight = calendar.getTimeInMillis() - System.currentTimeMillis();
+        final long today3AM = offsetFromTodayMidnight(0, 3).getTimeInMillis();
+        final long today4AM = offsetFromTodayMidnight(0, 4).getTimeInMillis();
+        final long tomorrow3AM = offsetFromTodayMidnight(1, 3).getTimeInMillis();
+
+        long nextScheduleTime;
+        if (System.currentTimeMillis() > today3AM && System.currentTimeMillis() < today4AM) {
+            nextScheduleTime = TimeUnit.SECONDS.toMillis(10);
+        } else {
+            nextScheduleTime = tomorrow3AM - System.currentTimeMillis(); // 3AM tomorrow
+        }
 
         JobInfo.Builder builder = new JobInfo.Builder(MOUNT_JOB_ID, sIdleService);
         builder.setRequiresDeviceIdle(true);
-        builder.setRequiresCharging(true);
-        builder.setMinimumLatency(timeToMidnight);
+        builder.setRequiresBatteryNotLow(true);
+        builder.setMinimumLatency(nextScheduleTime);
         tm.schedule(builder.build());
     }
 
-    private static Calendar tomorrowMidnight() {
+    private static Calendar offsetFromTodayMidnight(int nDays, int nHours) {
         Calendar calendar = Calendar.getInstance();
         calendar.setTimeInMillis(System.currentTimeMillis());
-        calendar.set(Calendar.HOUR_OF_DAY, 3);
+        calendar.set(Calendar.HOUR_OF_DAY, nHours);
         calendar.set(Calendar.MINUTE, 0);
         calendar.set(Calendar.SECOND, 0);
         calendar.set(Calendar.MILLISECOND, 0);
-        calendar.add(Calendar.DAY_OF_MONTH, 1);
+        calendar.add(Calendar.DAY_OF_MONTH, nDays);
         return calendar;
     }
 }
diff --git a/services/core/java/com/android/server/NetIdManager.java b/services/core/java/com/android/server/NetIdManager.java
index 11533be..097fb3a 100644
--- a/services/core/java/com/android/server/NetIdManager.java
+++ b/services/core/java/com/android/server/NetIdManager.java
@@ -20,6 +20,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * Class used to reserve and release net IDs.
@@ -38,14 +39,25 @@
     @GuardedBy("mNetIdInUse")
     private int mLastNetId = MIN_NET_ID - 1;
 
+    private final int mMaxNetId;
+
+    public NetIdManager() {
+        this(MAX_NET_ID);
+    }
+
+    @VisibleForTesting
+    NetIdManager(int maxNetId) {
+        mMaxNetId = maxNetId;
+    }
+
     /**
      * Get the first netId that follows the provided lastId and is available.
      */
-    private static int getNextAvailableNetIdLocked(
+    private int getNextAvailableNetIdLocked(
             int lastId, @NonNull SparseBooleanArray netIdInUse) {
         int netId = lastId;
-        for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
-            netId = netId < MAX_NET_ID ? netId + 1 : MIN_NET_ID;
+        for (int i = MIN_NET_ID; i <= mMaxNetId; i++) {
+            netId = netId < mMaxNetId ? netId + 1 : MIN_NET_ID;
             if (!netIdInUse.get(netId)) {
                 return netId;
             }
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 73ec561..bc50956 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,12 +29,12 @@
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -81,10 +81,14 @@
             "watchdog_explicit_health_check_enabled";
 
     // Duration to count package failures before it resets to 0
-    private static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
+    @VisibleForTesting
+    static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
             (int) TimeUnit.MINUTES.toMillis(1);
     // Number of package failures within the duration above before we notify observers
-    private static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+    @VisibleForTesting
+    static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
+    @VisibleForTesting
+    static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
     // Whether explicit health checks are enabled or not
     private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
 
@@ -117,6 +121,10 @@
     private final AtomicFile mPolicyFile;
     private final ExplicitHealthCheckController mHealthCheckController;
     private final ConnectivityModuleConnector mConnectivityModuleConnector;
+    private final Runnable mSyncRequests = this::syncRequests;
+    private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
+    private final Runnable mSaveToFile = this::saveToFile;
+    private final SystemClock mSystemClock;
     @GuardedBy("mLock")
     private boolean mIsPackagesReady;
     // Flag to control whether explicit health checks are supported or not
@@ -131,9 +139,11 @@
     @GuardedBy("mLock")
     private long mUptimeAtLastStateSync;
 
-    private final Runnable mSyncRequests = this::syncRequests;
-    private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
-    private final Runnable mSaveToFile = this::saveToFile;
+    @FunctionalInterface
+    @VisibleForTesting
+    interface SystemClock {
+        long uptimeMillis();
+    }
 
     private PackageWatchdog(Context context) {
         // Needs to be constructed inline
@@ -142,7 +152,8 @@
                                 "package-watchdog.xml")),
                 new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
                 new ExplicitHealthCheckController(context),
-                ConnectivityModuleConnector.getInstance());
+                ConnectivityModuleConnector.getInstance(),
+                android.os.SystemClock::uptimeMillis);
     }
 
     /**
@@ -151,13 +162,14 @@
     @VisibleForTesting
     PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
             Handler longTaskHandler, ExplicitHealthCheckController controller,
-            ConnectivityModuleConnector connectivityModuleConnector) {
+            ConnectivityModuleConnector connectivityModuleConnector, SystemClock clock) {
         mContext = context;
         mPolicyFile = policyFile;
         mShortTaskHandler = shortTaskHandler;
         mLongTaskHandler = longTaskHandler;
         mHealthCheckController = controller;
         mConnectivityModuleConnector = connectivityModuleConnector;
+        mSystemClock = clock;
         loadFromFile();
     }
 
@@ -197,7 +209,7 @@
         synchronized (mLock) {
             ObserverInternal internalObserver = mAllObservers.get(observer.getName());
             if (internalObserver != null) {
-                internalObserver.mRegisteredObserver = observer;
+                internalObserver.registeredObserver = observer;
             }
         }
     }
@@ -215,8 +227,10 @@
      * check state will be reset to a default depending on if the package is contained in
      * {@link mPackagesWithExplicitHealthCheckEnabled}.
      *
-     * @throws IllegalArgumentException if {@code packageNames} is empty
-     * or {@code durationMs} is less than 1
+     * <p>If {@code packageNames} is empty, this will be a no-op.
+     *
+     * <p>If {@code durationMs} is less than 1, a default monitoring duration
+     * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
      */
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
@@ -225,9 +239,9 @@
             return;
         }
         if (durationMs < 1) {
-            // TODO: Instead of failing, monitor for default? 48hrs?
-            throw new IllegalArgumentException("Invalid duration " + durationMs + "ms for observer "
+            Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
                     + observer.getName() + ". Not observing packages " + packageNames);
+            durationMs = DEFAULT_OBSERVING_DURATION_MS;
         }
 
         List<MonitoredPackage> packages = new ArrayList<>();
@@ -277,28 +291,6 @@
     }
 
     /**
-     * Returns packages observed by {@code observer}
-     *
-     * @return an empty set if {@code observer} has some packages observerd from a previous boot
-     * but has not registered itself in the current boot to receive notifications. Returns null
-     * if there are no active packages monitored from any boot.
-     */
-    @Nullable
-    public Set<String> getPackages(PackageHealthObserver observer) {
-        synchronized (mLock) {
-            for (int i = 0; i < mAllObservers.size(); i++) {
-                if (observer.getName().equals(mAllObservers.keyAt(i))) {
-                    if (observer.equals(mAllObservers.valueAt(i).mRegisteredObserver)) {
-                        return mAllObservers.valueAt(i).mPackages.keySet();
-                    }
-                    return Collections.emptySet();
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Called when a process fails either due to a crash or ANR.
      *
      * <p>For each package contained in the process, one registered observer with the least user
@@ -322,7 +314,7 @@
                     // Find observer with least user impact
                     for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                         ObserverInternal observer = mAllObservers.valueAt(oIndex);
-                        PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
+                        PackageHealthObserver registeredObserver = observer.registeredObserver;
                         if (registeredObserver != null
                                 && observer.onPackageFailureLocked(
                                         versionedPackage.getPackageName())) {
@@ -473,7 +465,7 @@
         synchronized (mLock) {
             for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
                 ObserverInternal observer = mAllObservers.valueAt(observerIdx);
-                MonitoredPackage monitoredPackage = observer.mPackages.get(packageName);
+                MonitoredPackage monitoredPackage = observer.packages.get(packageName);
 
                 if (monitoredPackage != null) {
                     int oldState = monitoredPackage.getHealthCheckStateLocked();
@@ -502,7 +494,7 @@
             Slog.d(TAG, "Received supported packages " + supportedPackages);
             Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
             while (oit.hasNext()) {
-                Iterator<MonitoredPackage> pit = oit.next().mPackages.values().iterator();
+                Iterator<MonitoredPackage> pit = oit.next().packages.values().iterator();
                 while (pit.hasNext()) {
                     MonitoredPackage monitoredPackage = pit.next();
                     String packageName = monitoredPackage.getName();
@@ -535,7 +527,7 @@
         while (oit.hasNext()) {
             ObserverInternal observer = oit.next();
             Iterator<MonitoredPackage> pit =
-                    observer.mPackages.values().iterator();
+                    observer.packages.values().iterator();
             while (pit.hasNext()) {
                 MonitoredPackage monitoredPackage = pit.next();
                 String packageName = monitoredPackage.getName();
@@ -579,7 +571,7 @@
             mUptimeAtLastStateSync = 0;
         } else {
             Slog.i(TAG, "Scheduling next state sync in " + durationMs + "ms");
-            mUptimeAtLastStateSync = SystemClock.uptimeMillis();
+            mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
             mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
         }
     }
@@ -593,7 +585,7 @@
     private long getNextStateSyncMillisLocked() {
         long shortestDurationMs = Long.MAX_VALUE;
         for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-            ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
+            ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).packages;
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                 MonitoredPackage mp = packages.valueAt(pIndex);
                 long duration = mp.getShortestScheduleDurationMsLocked();
@@ -612,7 +604,7 @@
     @GuardedBy("mLock")
     private void pruneObserversLocked() {
         long elapsedMs = mUptimeAtLastStateSync == 0
-                ? 0 : SystemClock.uptimeMillis() - mUptimeAtLastStateSync;
+                ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
         if (elapsedMs <= 0) {
             Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
             return;
@@ -627,8 +619,8 @@
             if (!failedPackages.isEmpty()) {
                 onHealthCheckFailed(observer, failedPackages);
             }
-            if (observer.mPackages.isEmpty()) {
-                Slog.i(TAG, "Discarding observer " + observer.mName + ". All packages expired");
+            if (observer.packages.isEmpty()) {
+                Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
                 it.remove();
             }
         }
@@ -638,7 +630,7 @@
             Set<MonitoredPackage> failedPackages) {
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
-                PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
+                PackageHealthObserver registeredObserver = observer.registeredObserver;
                 if (registeredObserver != null) {
                     Iterator<MonitoredPackage> it = failedPackages.iterator();
                     while (it.hasNext()) {
@@ -692,7 +684,7 @@
             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
                 ObserverInternal observer = ObserverInternal.read(parser, this);
                 if (observer != null) {
-                    mAllObservers.put(observer.mName, observer);
+                    mAllObservers.put(observer.name, observer);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -737,7 +729,7 @@
                     PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
                     DEFAULT_TRIGGER_FAILURE_DURATION_MS);
             if (mTriggerFailureDurationMs <= 0) {
-                mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_COUNT;
+                mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
             }
 
             setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
@@ -818,18 +810,16 @@
      * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
      * instances of this class.
      */
-    //TODO(b/120598832): Remove 'm' from non-private fields
     private static class ObserverInternal {
-        public final String mName;
-        //TODO(b/120598832): Add getter for mPackages
+        public final String name;
         @GuardedBy("mLock")
-        public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
+        public final ArrayMap<String, MonitoredPackage> packages = new ArrayMap<>();
         @Nullable
         @GuardedBy("mLock")
-        public PackageHealthObserver mRegisteredObserver;
+        public PackageHealthObserver registeredObserver;
 
         ObserverInternal(String name, List<MonitoredPackage> packages) {
-            mName = name;
+            this.name = name;
             updatePackagesLocked(packages);
         }
 
@@ -841,9 +831,9 @@
         public boolean writeLocked(XmlSerializer out) {
             try {
                 out.startTag(null, TAG_OBSERVER);
-                out.attribute(null, ATTR_NAME, mName);
-                for (int i = 0; i < mPackages.size(); i++) {
-                    MonitoredPackage p = mPackages.valueAt(i);
+                out.attribute(null, ATTR_NAME, name);
+                for (int i = 0; i < packages.size(); i++) {
+                    MonitoredPackage p = packages.valueAt(i);
                     p.writeLocked(out);
                 }
                 out.endTag(null, TAG_OBSERVER);
@@ -858,7 +848,7 @@
         public void updatePackagesLocked(List<MonitoredPackage> packages) {
             for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                 MonitoredPackage p = packages.get(pIndex);
-                mPackages.put(p.mName, p);
+                this.packages.put(p.mName, p);
             }
         }
 
@@ -875,13 +865,13 @@
         @GuardedBy("mLock")
         private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
             Set<MonitoredPackage> failedPackages = new ArraySet<>();
-            Iterator<MonitoredPackage> it = mPackages.values().iterator();
+            Iterator<MonitoredPackage> it = packages.values().iterator();
             while (it.hasNext()) {
                 MonitoredPackage p = it.next();
                 int oldState = p.getHealthCheckStateLocked();
                 int newState = p.handleElapsedTimeLocked(elapsedMs);
-                if (oldState != MonitoredPackage.STATE_FAILED
-                        && newState == MonitoredPackage.STATE_FAILED) {
+                if (oldState != HealthCheckState.FAILED
+                        && newState == HealthCheckState.FAILED) {
                     Slog.i(TAG, "Package " + p.mName + " failed health check");
                     failedPackages.add(p);
                 }
@@ -898,7 +888,7 @@
          */
         @GuardedBy("mLock")
         public boolean onPackageFailureLocked(String packageName) {
-            MonitoredPackage p = mPackages.get(packageName);
+            MonitoredPackage p = packages.get(packageName);
             if (p != null) {
                 return p.onFailureLocked();
             }
@@ -956,6 +946,23 @@
         }
     }
 
+    @Retention(SOURCE)
+    @IntDef(value = {
+            HealthCheckState.ACTIVE,
+            HealthCheckState.INACTIVE,
+            HealthCheckState.PASSED,
+            HealthCheckState.FAILED})
+    public @interface HealthCheckState {
+        // The package has not passed health check but has requested a health check
+        int ACTIVE = 0;
+        // The package has not passed health check and has not requested a health check
+        int INACTIVE = 1;
+        // The package has passed health check
+        int PASSED = 2;
+        // The package has failed health check
+        int FAILED = 3;
+    }
+
     /**
      * Represents a package and its health check state along with the time
      * it should be monitored for.
@@ -964,23 +971,15 @@
      * instances of this class.
      */
     class MonitoredPackage {
-        // Health check states
-        // TODO(b/120598832): Prefix with HEALTH_CHECK
-        // mName has not passed health check but has requested a health check
-        public static final int STATE_ACTIVE = 0;
-        // mName has not passed health check and has not requested a health check
-        public static final int STATE_INACTIVE = 1;
-        // mName has passed health check
-        public static final int STATE_PASSED = 2;
-        // mName has failed health check
-        public static final int STATE_FAILED = 3;
-
         //TODO(b/120598832): VersionedPackage?
         private final String mName;
+        // Times when package failures happen sorted in ascending order
+        @GuardedBy("mLock")
+        private final LongArrayQueue mFailureHistory = new LongArrayQueue();
         // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
         // methods that could change the health check state: handleElapsedTimeLocked and
         // tryPassHealthCheckLocked
-        private int mHealthCheckState = STATE_INACTIVE;
+        private int mHealthCheckState = HealthCheckState.INACTIVE;
         // Whether an explicit health check has passed.
         // This value in addition with mHealthCheckDurationMs determines the health check state
         // of the package, see #getHealthCheckStateLocked
@@ -996,12 +995,6 @@
         // of the package, see #getHealthCheckStateLocked
         @GuardedBy("mLock")
         private long mHealthCheckDurationMs = Long.MAX_VALUE;
-        // System uptime of first package failure
-        @GuardedBy("mLock")
-        private long mUptimeStartMs;
-        // Number of failures since mUptimeStartMs
-        @GuardedBy("mLock")
-        private int mFailures;
 
         MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
             this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
@@ -1036,20 +1029,17 @@
          */
         @GuardedBy("mLock")
         public boolean onFailureLocked() {
-            final long now = SystemClock.uptimeMillis();
-            final long duration = now - mUptimeStartMs;
-            if (duration > mTriggerFailureDurationMs) {
-                // TODO(b/120598832): Reseting to 1 is not correct
-                // because there may be more than 1 failure in the last trigger window from now
-                // This is the RescueParty impl, will leave for now
-                mFailures = 1;
-                mUptimeStartMs = now;
-            } else {
-                mFailures++;
+            // Sliding window algorithm: find out if there exists a window containing failures >=
+            // mTriggerFailureCount.
+            final long now = mSystemClock.uptimeMillis();
+            mFailureHistory.addLast(now);
+            while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
+                // Prune values falling out of the window
+                mFailureHistory.removeFirst();
             }
-            boolean failed = mFailures >= mTriggerFailureCount;
+            boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
             if (failed) {
-                mFailures = 0;
+                mFailureHistory.clear();
             }
             return failed;
         }
@@ -1067,7 +1057,7 @@
                         + ". Using total duration " + mDurationMs + "ms instead");
                 initialHealthCheckDurationMs = mDurationMs;
             }
-            if (mHealthCheckState == STATE_INACTIVE) {
+            if (mHealthCheckState == HealthCheckState.INACTIVE) {
                 // Transitions to ACTIVE
                 mHealthCheckDurationMs = initialHealthCheckDurationMs;
             }
@@ -1087,7 +1077,7 @@
             }
             // Transitions to FAILED if now <= 0 and health check not passed
             mDurationMs -= elapsedMs;
-            if (mHealthCheckState == STATE_ACTIVE) {
+            if (mHealthCheckState == HealthCheckState.ACTIVE) {
                 // We only update health check durations if we have #setHealthCheckActiveLocked
                 // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
                 // Transitions to FAILED if now <= 0 and health check not passed
@@ -1097,14 +1087,15 @@
         }
 
         /**
-         * Marks the health check as passed and transitions to {@link #STATE_PASSED}
-         * if not yet {@link #STATE_FAILED}.
+         * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
+         * if not yet {@link HealthCheckState.FAILED}.
          *
-         * @return the new health check state
+         * @return the new {@link HealthCheckState health check state}
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         public int tryPassHealthCheckLocked() {
-            if (mHealthCheckState != STATE_FAILED) {
+            if (mHealthCheckState != HealthCheckState.FAILED) {
                 // FAILED is a final state so only pass if we haven't failed
                 // Transition to PASSED
                 mHasPassedHealthCheck = true;
@@ -1117,12 +1108,11 @@
             return mName;
         }
 
-        //TODO(b/120598832): IntDef
         /**
-         * Returns the current health check state, any of {@link #STATE_ACTIVE},
-         * {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
+         * Returns the current {@link HealthCheckState health check state}.
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         public int getHealthCheckStateLocked() {
             return mHealthCheckState;
         }
@@ -1155,28 +1145,30 @@
          */
         @GuardedBy("mLock")
         public boolean isPendingHealthChecksLocked() {
-            return mHealthCheckState == STATE_ACTIVE || mHealthCheckState == STATE_INACTIVE;
+            return mHealthCheckState == HealthCheckState.ACTIVE
+                    || mHealthCheckState == HealthCheckState.INACTIVE;
         }
 
         /**
          * Updates the health check state based on {@link #mHasPassedHealthCheck}
          * and {@link #mHealthCheckDurationMs}.
          *
-         * @return the new health check state
+         * @return the new {@link HealthCheckState health check state}
          */
         @GuardedBy("mLock")
+        @HealthCheckState
         private int updateHealthCheckStateLocked() {
             int oldState = mHealthCheckState;
             if (mHasPassedHealthCheck) {
                 // Set final state first to avoid ambiguity
-                mHealthCheckState = STATE_PASSED;
+                mHealthCheckState = HealthCheckState.PASSED;
             } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
                 // Set final state first to avoid ambiguity
-                mHealthCheckState = STATE_FAILED;
+                mHealthCheckState = HealthCheckState.FAILED;
             } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
-                mHealthCheckState = STATE_INACTIVE;
+                mHealthCheckState = HealthCheckState.INACTIVE;
             } else {
-                mHealthCheckState = STATE_ACTIVE;
+                mHealthCheckState = HealthCheckState.ACTIVE;
             }
             Slog.i(TAG, "Updated health check state for package " + mName + ": "
                     + toString(oldState) + " -> " + toString(mHealthCheckState));
@@ -1184,15 +1176,15 @@
         }
 
         /** Returns a {@link String} representation of the current health check state. */
-        private String toString(int state) {
+        private String toString(@HealthCheckState int state) {
             switch (state) {
-                case STATE_ACTIVE:
+                case HealthCheckState.ACTIVE:
                     return "ACTIVE";
-                case STATE_INACTIVE:
+                case HealthCheckState.INACTIVE:
                     return "INACTIVE";
-                case STATE_PASSED:
+                case HealthCheckState.PASSED:
                     return "PASSED";
-                case STATE_FAILED:
+                case HealthCheckState.FAILED:
                     return "FAILED";
                 default:
                     return "UNKNOWN";
diff --git a/services/core/java/com/android/server/RecoverySystemService.java b/services/core/java/com/android/server/RecoverySystemService.java
index 1517887..997178e 100644
--- a/services/core/java/com/android/server/RecoverySystemService.java
+++ b/services/core/java/com/android/server/RecoverySystemService.java
@@ -25,15 +25,12 @@
 import android.os.RecoverySystem;
 import android.os.RemoteException;
 import android.os.SystemProperties;
-import android.system.ErrnoException;
-import android.system.Os;
 import android.util.Slog;
 
 import libcore.io.IoUtils;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
-import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
 
@@ -288,7 +285,6 @@
                     byte[] cmdUtf8 = command.getBytes("UTF-8");
                     dos.writeInt(cmdUtf8.length);
                     dos.write(cmdUtf8, 0, cmdUtf8.length);
-                    dos.flush();
                 }
 
                 // Read the status from the socket.
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 4151c72..5e659b6 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -168,7 +168,7 @@
      * calls this method).
      */
     @Deprecated
-    public void onStartUser(@UserIdInt int userHandle) {}
+    public void onStartUser(@UserIdInt int userId) {}
 
     /**
      * Called when a new user is starting, for system services to initialize any per-user
@@ -189,7 +189,7 @@
      * default calls this method).
      */
     @Deprecated
-    public void onUnlockUser(@UserIdInt int userHandle) {}
+    public void onUnlockUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is in the process of being unlocked. This
@@ -218,7 +218,7 @@
      * (which by default calls this method).
      */
     @Deprecated
-    public void onSwitchUser(@UserIdInt int userHandle) {}
+    public void onSwitchUser(@UserIdInt int userId) {}
 
     /**
      * Called when switching to a different foreground user, for system services that have
@@ -243,7 +243,7 @@
      * calls this method).
      */
     @Deprecated
-    public void onStopUser(@UserIdInt int userHandle) {}
+    public void onStopUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is stopping, for system services to finalize any per-user
@@ -268,7 +268,7 @@
      * default calls this method).
      */
     @Deprecated
-    public void onCleanupUser(@UserIdInt int userHandle) {}
+    public void onCleanupUser(@UserIdInt int userId) {}
 
     /**
      * Called when an existing user is stopping, for system services to finalize any per-user
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index f7e825e..eb2723a 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -79,7 +79,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
-import java.util.stream.Collectors;
 
 /**
  * Since phone process can be restarted, this class provides a centralized place
@@ -849,10 +848,7 @@
                         }
                     }
                     if ((events & PhoneStateListener
-                            .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0
-                            && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                                    r.context, r.callerPid, r.callerUid, r.callingPackage,
-                            "listen_active_data_subid_change")) {
+                            .LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE) != 0) {
                         try {
                             r.callback.onActiveDataSubIdChanged(mActiveDataSubId);
                         } catch (RemoteException ex) {
@@ -1829,23 +1825,11 @@
             log("notifyActiveDataSubIdChanged: activeDataSubId=" + activeDataSubId);
         }
 
-        // Create a copy to prevent the IPC call while checking carrier privilege under the lock.
-        List<Record> copiedRecords;
-        synchronized (mRecords) {
-            copiedRecords = new ArrayList<>(mRecords);
-        }
         mActiveDataSubId = activeDataSubId;
-
-        // Filter the record that does not listen to this change or does not have the permission.
-        copiedRecords = copiedRecords.stream().filter(r -> r.matchPhoneStateListenerEvent(
-                PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)
-                && TelephonyPermissions.checkReadPhoneStateOnAnyActiveSub(
-                        mContext, r.callerPid, r.callerUid, r.callingPackage,
-                "notifyActiveDataSubIdChanged")).collect(Collectors.toCollection(ArrayList::new));
-
         synchronized (mRecords) {
-            for (Record r : copiedRecords) {
-                if (mRecords.contains(r)) {
+            for (Record r : mRecords) {
+                if (r.matchPhoneStateListenerEvent(
+                        PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE)) {
                     try {
                         r.callback.onActiveDataSubIdChanged(activeDataSubId);
                     } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 8eb5d9b..e7569be 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -829,6 +829,15 @@
         }
     }
 
+    void killMisbehavingService(ServiceRecord r,
+            int appUid, int appPid, String localPackageName) {
+        synchronized (mAm) {
+            stopServiceLocked(r);
+            mAm.crashApplication(appUid, appPid, localPackageName, -1,
+                    "Bad notification for startForeground", true /*force*/);
+        }
+    }
+
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
@@ -1739,8 +1748,8 @@
                     s.instanceName, s.processName);
             // Once the apps have become associated, if one of them is caller is ephemeral
             // the target app should now be able to see the calling app
-            mAm.grantEphemeralAccessLocked(callerApp.userId, service,
-                    UserHandle.getAppId(s.appInfo.uid), UserHandle.getAppId(callerApp.uid));
+            mAm.grantImplicitAccess(callerApp.userId, service,
+                    UserHandle.getAppId(callerApp.uid), UserHandle.getAppId(s.appInfo.uid));
 
             AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
             ConnectionRecord c = new ConnectionRecord(b, activity,
@@ -2793,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, UserHandle.getAppId(si.callingId),
+                    UserHandle.getAppId(r.appInfo.uid)
+            );
             bumpServiceExecutingLocked(r, execInFg, "start");
             if (!oomAdjusted) {
                 oomAdjusted = true;
@@ -3930,7 +3940,7 @@
     void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) {
         mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
                 "Context.startForegroundService() did not then call Service.startForeground(): "
-                    + serviceRecord);
+                    + serviceRecord, false /*force*/);
     }
 
     void scheduleServiceTimeoutLocked(ProcessRecord proc) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3dc33f8..3c7cb88 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3568,7 +3568,7 @@
 
     @Override
     public void crashApplication(int uid, int initialPid, String packageName, int userId,
-            String message) {
+            String message, boolean force) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: crashApplication() from pid="
@@ -3580,7 +3580,8 @@
         }
 
         synchronized(this) {
-            mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, message);
+            mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId,
+                    message, force);
         }
     }
 
@@ -5264,11 +5265,14 @@
 
         // Inform checkpointing systems of success
         try {
+            // This line is needed to CTS test for the correct exception handling
+            // See b/138952436#comment36 for context
+            Slog.i(TAG, "About to commit checkpoint");
             IStorageManager storageManager = PackageHelper.getStorageManager();
             storageManager.commitChanges();
         } catch (Exception e) {
             PowerManager pm = (PowerManager)
-                     mContext.getSystemService(Context.POWER_SERVICE);
+                     mInjector.getContext().getSystemService(Context.POWER_SERVICE);
             pm.reboot("Checkpoint commit failed");
         }
 
@@ -6097,16 +6101,16 @@
         return ptw != null ? ptw.tag : null;
     }
 
-    private ProviderInfo getProviderInfoLocked(String authority, int userHandle, int pmFlags) {
+    private ProviderInfo getProviderInfoLocked(String authority, @UserIdInt int userId,
+            int pmFlags) {
         ProviderInfo pi = null;
-        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
+        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId);
         if (cpr != null) {
             pi = cpr.info;
         } else {
             try {
                 pi = AppGlobals.getPackageManager().resolveContentProvider(
-                        authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags,
-                        userHandle);
+                        authority, PackageManager.GET_URI_PERMISSION_PATTERNS | pmFlags, userId);
             } catch (RemoteException ex) {
             }
         }
@@ -6114,10 +6118,9 @@
     }
 
     @VisibleForTesting
-    public void grantEphemeralAccessLocked(int userId, Intent intent,
-            int targetAppId, int ephemeralAppId) {
+    public void grantImplicitAccess(int userId, Intent intent, int callingAppId, int targetAppId) {
         getPackageManagerInternalLocked().
-                grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
+                grantImplicitAccess(userId, intent, callingAppId, targetAppId);
     }
 
     /**
@@ -6323,13 +6326,6 @@
     }
 
     @Override
-    public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration) {
-        mActivityTaskManager.resizeStack(stackId, destBounds, allowResizeInDockedMode,
-                preserveWindows, animate, animationDuration);
-    }
-
-    @Override
     public ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
             int userId) {
         return mActivityTaskManager.getRecentTasks(maxNum, flags, userId);
@@ -7094,9 +7090,10 @@
             }
             checkTime(startTime, "getContentProviderImpl: done!");
 
-            grantEphemeralAccessLocked(userId, null /*intent*/,
-                    UserHandle.getAppId(cpi.applicationInfo.uid),
-                    UserHandle.getAppId(Binder.getCallingUid()));
+            grantImplicitAccess(userId, null /*intent*/,
+                    UserHandle.getAppId(Binder.getCallingUid()),
+                    UserHandle.getAppId(cpi.applicationInfo.uid)
+            );
         }
 
         // Wait for the provider to be published...
@@ -8203,10 +8200,12 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
+    // TODO(b/137825297): Remove deprecated annotation and rephrase comments for all
+    // requestBugreport functions below.
     @Deprecated
     @Override
     public void requestBugReport(int bugreportType) {
@@ -8214,11 +8213,12 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
+    @Override
     public void requestBugReportWithDescription(@Nullable String shareTitle,
             @Nullable String shareDescription, int bugreportType) {
         String type = null;
@@ -8299,8 +8299,8 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
@@ -8311,8 +8311,8 @@
     }
 
     /**
-     * @deprecated This method is only used by a few internal components and it will soon be
-     * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+     * @deprecated This method is only used by a few internal components and it will soon start
+     * using bug report API (which will be restricted to a few, pre-defined apps).
      * No new code should be calling it.
      */
     @Deprecated
@@ -8322,6 +8322,41 @@
                 ActivityManager.BUGREPORT_OPTION_WIFI);
     }
 
+    /**
+     * Takes an interactive bugreport with a progress notification
+     */
+    @Override
+    public void requestInteractiveBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+    }
+
+    /**
+     * Takes an interactive bugreport with a progress notification. Also, shows the given title and
+     * description on the final share notification
+     */
+    @Override
+    public void requestInteractiveBugReportWithDescription(String shareTitle,
+            String shareDescription) {
+        requestBugReportWithDescription(shareTitle, shareDescription,
+                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+    }
+
+    /**
+     * Takes a bugreport with minimal user interference
+     */
+    @Override
+    public void requestFullBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_FULL);
+    }
+
+    /**
+     * Takes a bugreport remotely
+     */
+    @Override
+    public void requestRemoteBugReport() {
+        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_REMOTE);
+    }
+
     public void registerProcessObserver(IProcessObserver observer) {
         enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerProcessObserver()");
@@ -9131,7 +9166,16 @@
                 Integer.toString(currentUserId), currentUserId);
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
                 Integer.toString(currentUserId), currentUserId);
-        mSystemServiceManager.startUser(t, currentUserId);
+
+        // On Automotive, at this point the system user has already been started and unlocked,
+        // and some of the tasks we do here have already been done. So skip those in that case.
+        // TODO(b/132262830): this workdound shouldn't be necessary once we move the
+        // headless-user start logic to UserManager-land
+        final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
+
+        if (bootingSystemUser) {
+            mSystemServiceManager.startUser(t, currentUserId);
+        }
 
         synchronized (this) {
             // Only start up encryption-aware persistent apps; once user is
@@ -9160,12 +9204,6 @@
                 t.traceEnd();
             }
 
-            // On Automotive, at this point the system user has already been started and unlocked,
-            // and some of the tasks we do here have already been done. So skip those in that case.
-            // TODO(b/132262830): this workdound shouldn't be necessary once we move the
-            // headless-user start logic to UserManager-land
-            final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
             if (bootingSystemUser) {
                 t.traceBegin("startHomeOnAllDisplays");
                 mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
@@ -15227,7 +15265,12 @@
                             final int uid = getUidFromIntent(intent);
                             if (uid >= 0) {
                                 mBatteryStatsService.removeUid(uid);
-                                mAppOpsService.uidRemoved(uid);
+                                if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mAppOpsService.resetAllModes(UserHandle.getUserId(uid),
+                                            intent.getData().getSchemeSpecificPart());
+                                } else {
+                                    mAppOpsService.uidRemoved(uid);
+                                }
                             }
                             break;
                         case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
@@ -15306,11 +15349,11 @@
                                     intent.getAction());
                             final String[] packageNames = intent.getStringArrayExtra(
                                     Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                            final int userHandle = intent.getIntExtra(
+                            final int userIdExtra = intent.getIntExtra(
                                     Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
 
                             mAtmInternal.onPackagesSuspendedChanged(packageNames, suspended,
-                                    userHandle);
+                                    userIdExtra);
                             break;
                     }
                     break;
@@ -18029,7 +18072,7 @@
         }
 
         @Override
-        public void killForegroundAppsForUser(int userHandle) {
+        public void killForegroundAppsForUser(@UserIdInt int userId) {
             synchronized (ActivityManagerService.this) {
                 final ArrayList<ProcessRecord> procs = new ArrayList<>();
                 final int NP = mProcessList.mProcessNames.getMap().size();
@@ -18044,7 +18087,7 @@
                             continue;
                         }
                         if (app.removed
-                                || (app.userId == userHandle && app.hasForegroundActivities())) {
+                                || (app.userId == userId && app.hasForegroundActivities())) {
                             procs.add(app);
                         }
                     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a0900b6..d46c626 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1070,7 +1070,7 @@
         } catch (NumberFormatException e) {
             packageName = arg;
         }
-        mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash");
+        mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash", false);
         return 0;
     }
 
@@ -2481,8 +2481,6 @@
         switch (op) {
             case "move-task":
                 return runStackMoveTask(pw);
-            case "resize":
-                return runStackResize(pw);
             case "resize-animated":
                 return runStackResizeAnimated(pw);
             case "resize-docked-stack":
@@ -2561,17 +2559,6 @@
         return 0;
     }
 
-    int runStackResize(PrintWriter pw) throws RemoteException {
-        String stackIdStr = getNextArgRequired();
-        int stackId = Integer.parseInt(stackIdStr);
-        final Rect bounds = getBounds();
-        if (bounds == null) {
-            getErrPrintWriter().println("Error: invalid input bounds");
-            return -1;
-        }
-        return resizeStack(stackId, bounds, 0);
-    }
-
     int runStackResizeAnimated(PrintWriter pw) throws RemoteException {
         String stackIdStr = getNextArgRequired();
         int stackId = Integer.parseInt(stackIdStr);
@@ -2585,16 +2572,7 @@
                 return -1;
             }
         }
-        return resizeStackUnchecked(stackId, bounds, 0, true);
-    }
-
-    int resizeStackUnchecked(int stackId, Rect bounds, int delayMs, boolean animate)
-            throws RemoteException {
-        try {
-            mTaskInterface.resizeStack(stackId, bounds, false, false, animate, -1);
-            Thread.sleep(delayMs);
-        } catch (InterruptedException e) {
-        }
+        mTaskInterface.animateResizePinnedStack(stackId, bounds, -1);
         return 0;
     }
 
@@ -2609,14 +2587,6 @@
         return 0;
     }
 
-    int resizeStack(int stackId, Rect bounds, int delayMs) throws RemoteException {
-        if (bounds == null) {
-            getErrPrintWriter().println("Error: invalid input bounds");
-            return -1;
-        }
-        return resizeStackUnchecked(stackId, bounds, delayMs, false);
-    }
-
     int runStackPositionTask(PrintWriter pw) throws RemoteException {
         String taskIdStr = getNextArgRequired();
         int taskId = Integer.parseInt(taskIdStr);
@@ -3195,8 +3165,6 @@
             pw.println("       move-task <TASK_ID> <STACK_ID> [true|false]");
             pw.println("           Move <TASK_ID> from its current stack to the top (true) or");
             pw.println("           bottom (false) of <STACK_ID>.");
-            pw.println("       resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("           Change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>.");
             pw.println("       resize-animated <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           Same as resize, but allow animation.");
             pw.println("       resize-docked-stack <LEFT,TOP,RIGHT,BOTTOM> [<TASK_LEFT,TASK_TOP,TASK_RIGHT,TASK_BOTTOM>]");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 1ff6f4d..6a29c75 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -314,20 +314,24 @@
     }
 
     void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
-        app.setCrashing(false);
-        app.crashingReport = null;
-        app.setNotResponding(false);
-        app.notRespondingReport = null;
         if (app.anrDialog == fromDialog) {
             app.anrDialog = null;
         }
         if (app.waitDialog == fromDialog) {
             app.waitDialog = null;
         }
+        killAppImmediateLocked(app, "user-terminated", "user request after error");
+    }
+
+    private void killAppImmediateLocked(ProcessRecord app, String reason, String killReason) {
+        app.setCrashing(false);
+        app.crashingReport = null;
+        app.setNotResponding(false);
+        app.notRespondingReport = null;
         if (app.pid > 0 && app.pid != MY_PID) {
-            handleAppCrashLocked(app, "user-terminated" /*reason*/,
+            handleAppCrashLocked(app, reason,
                     null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
-            app.kill("user request after error", true);
+            app.kill(killReason, true);
         }
     }
 
@@ -341,7 +345,7 @@
      * @param message
      */
     void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId,
-            String message) {
+            String message, boolean force) {
         ProcessRecord proc = null;
 
         // Figure out which process to kill.  We don't trust that initialPid
@@ -374,6 +378,14 @@
         }
 
         proc.scheduleCrash(message);
+        if (force) {
+            // If the app is responsive, the scheduled crash will happen as expected
+            // and then the delayed summary kill will be a no-op.
+            final ProcessRecord p = proc;
+            mService.mHandler.postDelayed(
+                    () -> killAppImmediateLocked(p, "forced", "killed for invalid state"),
+                    5000L);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java
index 95eb2c69..fd64df9 100644
--- a/services/core/java/com/android/server/am/MemoryStatUtil.java
+++ b/services/core/java/com/android/server/am/MemoryStatUtil.java
@@ -26,17 +26,12 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -61,8 +56,6 @@
     private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status";
     /** Path to procfs cmdline file. Used with pid: /proc/pid/cmdline. */
     private static final String PROC_CMDLINE_FILE_FMT = "/proc/%d/cmdline";
-    /** Path to debugfs file for the system ion heap. */
-    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
 
     private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)");
     private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)");
@@ -70,8 +63,6 @@
     private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
     private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");
 
-    private static final Pattern RSS_HIGH_WATERMARK_IN_KILOBYTES =
-            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_RSS_IN_KILOBYTES =
             Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
     private static final Pattern PROCFS_ANON_RSS_IN_KILOBYTES =
@@ -79,11 +70,6 @@
     private static final Pattern PROCFS_SWAP_IN_KILOBYTES =
             Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB");
 
-    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
-    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
-            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
-
     private static final int PGFAULT_INDEX = 9;
     private static final int PGMAJFAULT_INDEX = 11;
     private static final int START_TIME_INDEX = 21;
@@ -125,15 +111,6 @@
     }
 
     /**
-     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
-     * /proc/PID/status in bytes or 0 if not available.
-     */
-    public static long readRssHighWaterMarkFromProcfs(int pid) {
-        final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
-        return parseVmHWMFromProcfs(readFileContents(statusPath));
-    }
-
-    /**
      * Reads cmdline of a process from procfs.
      *
      * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
@@ -144,26 +121,6 @@
         return parseCmdlineFromProcfs(readFileContents(path));
     }
 
-    /**
-     * Reads size of the system ion heap from debugfs.
-     *
-     * Returns value of the total size in bytes of the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    public static long readSystemIonHeapSizeFromDebugfs() {
-        return parseIonHeapSizeFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
-    /**
-     * Reads process allocation sizes on the system ion heap from debugfs.
-     *
-     * Returns values of allocation sizes in bytes on the system ion heap from
-     * /sys/kernel/debug/ion/heaps/system.
-     */
-    public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
-        return parseProcessIonHeapSizesFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
-    }
-
     private static String readFileContents(String path) {
         final File file = new File(path);
         if (!file.exists()) {
@@ -236,21 +193,6 @@
     }
 
     /**
-     * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The
-     * returned value is in bytes.
-     */
-    @VisibleForTesting
-    static long parseVmHWMFromProcfs(String procStatusContents) {
-        if (procStatusContents == null || procStatusContents.isEmpty()) {
-            return 0;
-        }
-        // Convert value read from /proc/pid/status from kilobytes to bytes.
-        return tryParseLong(RSS_HIGH_WATERMARK_IN_KILOBYTES, procStatusContents)
-                * BYTES_IN_KILOBYTE;
-    }
-
-
-    /**
      * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
      *
      * Parsing is required to strip anything after first null byte.
@@ -268,55 +210,6 @@
     }
 
     /**
-     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
-     * debugfs. The returned value is in bytes.
-     */
-    @VisibleForTesting
-    static long parseIonHeapSizeFromDebugfs(String contents) {
-        if (contents == null || contents.isEmpty()) {
-            return 0;
-        }
-        return tryParseLong(ION_HEAP_SIZE_IN_BYTES, contents);
-    }
-
-    /**
-     * Parses per-process allocation sizes on the ion heap from the contents of a file under
-     * /sys/kernel/debug/ion/heaps in debugfs.
-     */
-    @VisibleForTesting
-    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
-        if (contents == null || contents.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
-        final SparseArray<IonAllocations> entries = new SparseArray<>();
-        while (m.find()) {
-            try {
-                final int pid = Integer.parseInt(m.group(1));
-                final long sizeInBytes = Long.parseLong(m.group(2));
-                IonAllocations allocations = entries.get(pid);
-                if (allocations == null) {
-                    allocations = new IonAllocations();
-                    entries.put(pid, allocations);
-                }
-                allocations.pid = pid;
-                allocations.totalSizeInBytes += sizeInBytes;
-                allocations.count += 1;
-                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
-            } catch (NumberFormatException e) {
-                Slog.e(TAG, "Failed to parse value", e);
-            }
-        }
-
-        final List<IonAllocations> result = new ArrayList<>(entries.size());
-        for (int i = 0; i < entries.size(); i++) {
-            result.add(entries.valueAt(i));
-        }
-        return result;
-    }
-
-    /**
      * Returns whether per-app memcg is available on device.
      */
     static boolean hasMemcg() {
@@ -353,40 +246,4 @@
         /** Device time when the processes started. */
         public long startTimeNanos;
     }
-
-    /** Summary information about process ion allocations. */
-    public static final class IonAllocations {
-        /** PID these allocations belong to. */
-        public int pid;
-        /** Size of all individual allocations added together. */
-        public long totalSizeInBytes;
-        /** Number of allocations. */
-        public int count;
-        /** Size of the largest allocation. */
-        public long maxSizeInBytes;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            IonAllocations that = (IonAllocations) o;
-            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
-                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
-        }
-
-        @Override
-        public String toString() {
-            return "IonAllocations{"
-                    + "pid=" + pid
-                    + ", totalSizeInBytes=" + totalSizeInBytes
-                    + ", count=" + count
-                    + ", maxSizeInBytes=" + maxSizeInBytes
-                    + '}';
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 0509f9f..2f9a5c9 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -12,6 +12,7 @@
 varunshah@google.com
 kwekua@google.com
 bookatz@google.com
+jji@google.com
 
 # Windows & Activities
 ogunwale@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7a3d3c2..3c2aee4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1576,7 +1576,7 @@
         }
 
         if (adj == ProcessList.SERVICE_ADJ) {
-            if (doingAll) {
+            if (doingAll && !cycleReEval) {
                 app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
                 mNewNumServiceProcs++;
                 //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 8c038aa..cc4b160 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -803,6 +803,7 @@
             final String localPackageName = packageName;
             final int localForegroundId = foregroundId;
             final Notification _foregroundNoti = foregroundNoti;
+            final ServiceRecord record = this;
             ams.mHandler.post(new Runnable() {
                 public void run() {
                     NotificationManagerInternal nm = LocalServices.getService(
@@ -901,10 +902,8 @@
                         Slog.w(TAG, "Error showing notification for service", e);
                         // If it gave us a garbage notification, it doesn't
                         // get to be foreground.
-                        ams.setServiceForeground(instanceName, ServiceRecord.this,
-                                0, null, 0, 0);
-                        ams.crashApplication(appUid, appPid, localPackageName, -1,
-                                "Bad notification for startForeground: " + e);
+                        ams.mServices.killMisbehavingService(record,
+                                appUid, appPid, localPackageName);
                     }
                 }
             });
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 5fe72dd..5c8e530 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -841,7 +841,7 @@
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
-    private int updateUserToLockLU(int userId) {
+    private int updateUserToLockLU(@UserIdInt int userId) {
         int userIdToLock = userId;
         if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
                 && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
@@ -869,7 +869,7 @@
      * {@code userId}. The returned list includes {@code userId}.
      */
     @GuardedBy("mLock")
-    private @NonNull int[] getUsersToStopLU(int userId) {
+    private @NonNull int[] getUsersToStopLU(@UserIdInt int userId) {
         int startedUsersSize = mStartedUsers.size();
         IntArray userIds = new IntArray();
         userIds.add(userId);
@@ -892,7 +892,7 @@
         return userIds.toArray();
     }
 
-    private void forceStopUser(int userId, String reason) {
+    private void forceStopUser(@UserIdInt int userId, String reason) {
         mInjector.activityManagerForceStopPackage(userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
@@ -965,7 +965,7 @@
         }
     }
 
-    boolean startUser(final int userId, final boolean foreground) {
+    boolean startUser(final @UserIdInt int userId, final boolean foreground) {
         return startUser(userId, foreground, null);
     }
 
@@ -1002,7 +1002,7 @@
      * @return true if the user has been successfully started
      */
     boolean startUser(
-            final int userId,
+            final @UserIdInt int userId,
             final boolean foreground,
             @Nullable IProgressListener unlockListener) {
 
@@ -1018,7 +1018,7 @@
         }
     }
 
-    private boolean startUserInternal(int userId, boolean foreground,
+    private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
             @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
         Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);
 
@@ -1257,7 +1257,8 @@
         }
     }
 
-    boolean unlockUser(final int userId, byte[] token, byte[] secret, IProgressListener listener) {
+    boolean unlockUser(final @UserIdInt int userId, byte[] token, byte[] secret,
+            IProgressListener listener) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "unlockUser");
         final long binderToken = Binder.clearCallingIdentity();
         try {
@@ -1273,12 +1274,12 @@
      * when the credential-encrypted storage isn't tied to a user-provided
      * PIN or pattern.
      */
-    private boolean maybeUnlockUser(final int userId) {
+    private boolean maybeUnlockUser(final @UserIdInt int userId) {
         // Try unlocking storage using empty token
         return unlockUserCleared(userId, null, null, null);
     }
 
-    private static void notifyFinished(int userId, IProgressListener listener) {
+    private static void notifyFinished(@UserIdInt int userId, IProgressListener listener) {
         if (listener == null) return;
         try {
             listener.onFinished(userId, null);
@@ -1286,7 +1287,7 @@
         }
     }
 
-    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+    private boolean unlockUserCleared(final @UserIdInt int userId, byte[] token, byte[] secret,
             IProgressListener listener) {
         UserState uss;
         if (!StorageManager.isUserKeyUnlocked(userId)) {
@@ -1384,7 +1385,7 @@
                 getSwitchingFromSystemUserMessage(), getSwitchingToSystemUserMessage());
     }
 
-    private void dispatchForegroundProfileChanged(int userId) {
+    private void dispatchForegroundProfileChanged(@UserIdInt int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1397,7 +1398,7 @@
     }
 
     /** Called on handler thread */
-    void dispatchUserSwitchComplete(int userId) {
+    void dispatchUserSwitchComplete(@UserIdInt int userId) {
         mInjector.getWindowManager().setSwitchingUser(false);
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
@@ -1409,7 +1410,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    private void dispatchLockedBootComplete(int userId) {
+    private void dispatchLockedBootComplete(@UserIdInt int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1596,7 +1597,7 @@
     }
 
 
-    int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+    int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId, boolean allowAll,
             int allowMode, String name, String callerPackage) {
         final int callingUserId = UserHandle.getUserId(callingUid);
         if (callingUserId == userId) {
@@ -1682,12 +1683,12 @@
         return targetUserId;
     }
 
-    int unsafeConvertIncomingUser(int userId) {
+    int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
     }
 
-    void ensureNotSpecialUser(int userId) {
+    void ensureNotSpecialUser(@UserIdInt int userId) {
         if (userId >= 0) {
             return;
         }
@@ -1700,7 +1701,7 @@
         mUserSwitchObservers.register(observer, name);
     }
 
-    void sendForegroundProfileChanged(int userId) {
+    void sendForegroundProfileChanged(@UserIdInt int userId) {
         mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
         mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
     }
@@ -1709,13 +1710,13 @@
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserState(int userId) {
+    UserState getStartedUserState(@UserIdInt int userId) {
         synchronized (mLock) {
             return mStartedUsers.get(userId);
         }
     }
 
-    boolean hasStartedUserState(int userId) {
+    boolean hasStartedUserState(@UserIdInt int userId) {
         synchronized (mLock) {
             return mStartedUsers.get(userId) != null;
         }
@@ -1804,7 +1805,7 @@
         }
     }
 
-    boolean isUserRunning(int userId, int flags) {
+    boolean isUserRunning(@UserIdInt int userId, int flags) {
         UserState state = getStartedUserState(userId);
         if (state == null) {
             return false;
@@ -1920,7 +1921,7 @@
     }
 
     @GuardedBy("mLock")
-    private boolean isCurrentUserLU(int userId) {
+    private boolean isCurrentUserLU(@UserIdInt int userId) {
         return userId == getCurrentOrTargetUserIdLU();
     }
 
@@ -1929,7 +1930,7 @@
         return ums != null ? ums.getUserIds() : new int[] { 0 };
     }
 
-    private UserInfo getUserInfo(int userId) {
+    private UserInfo getUserInfo(@UserIdInt int userId) {
         return mInjector.getUserManager().getUserInfo(userId);
     }
 
@@ -1943,7 +1944,7 @@
      *
      * It doesn't handle other special user IDs such as {@link UserHandle#USER_CURRENT}.
      */
-    int[] expandUserId(int userId) {
+    int[] expandUserId(@UserIdInt int userId) {
         if (userId != UserHandle.USER_ALL) {
             return new int[] {userId};
         } else {
@@ -1951,7 +1952,7 @@
         }
     }
 
-    boolean exists(int userId) {
+    boolean exists(@UserIdInt int userId) {
         return mInjector.getUserManager().exists(userId);
     }
 
@@ -1967,16 +1968,16 @@
         }
     }
 
-    private void enforceShellRestriction(String restriction, int userHandle) {
+    private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
         if (Binder.getCallingUid() == SHELL_UID) {
-            if (userHandle < 0 || hasUserRestriction(restriction, userHandle)) {
+            if (userId < 0 || hasUserRestriction(restriction, userId)) {
                 throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
+                        + userId);
             }
         }
     }
 
-    boolean hasUserRestriction(String restriction, int userId) {
+    boolean hasUserRestriction(String restriction, @UserIdInt int userId) {
         return mInjector.getUserManager().hasUserRestriction(restriction, userId);
     }
 
@@ -1994,7 +1995,7 @@
         }
     }
 
-    boolean isUserOrItsParentRunning(int userId) {
+    boolean isUserOrItsParentRunning(@UserIdInt int userId) {
         synchronized (mLock) {
             if (isUserRunning(userId, 0)) {
                 return true;
@@ -2007,7 +2008,7 @@
         }
     }
 
-    boolean isCurrentProfile(int userId) {
+    boolean isCurrentProfile(@UserIdInt int userId) {
         synchronized (mLock) {
             return ArrayUtils.contains(mCurrentProfileIds, userId);
         }
@@ -2019,7 +2020,7 @@
         }
     }
 
-    void onUserRemoved(int userId) {
+    void onUserRemoved(@UserIdInt int userId) {
         synchronized (mLock) {
             int size = mUserProfileGroupIds.size();
             for (int i = size - 1; i >= 0; i--) {
@@ -2037,7 +2038,7 @@
      * Returns whether the given user requires credential entry at this time. This is used to
      * intercept activity launches for work apps when the Work Challenge is present.
      */
-    protected boolean shouldConfirmCredentials(int userId) {
+    protected boolean shouldConfirmCredentials(@UserIdInt int userId) {
         synchronized (mLock) {
             if (mStartedUsers.get(userId) == null) {
                 return false;
@@ -2265,7 +2266,7 @@
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
-                int realCallingPid, int userId) {
+                int realCallingPid, @UserIdInt int userId) {
             // TODO b/64165549 Verify that mLock is not held before calling AMS methods
             synchronized (mService) {
                 return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
@@ -2282,11 +2283,11 @@
         WindowManagerService getWindowManager() {
             return mService.mWindowManager;
         }
-        void activityManagerOnUserStopped(int userId) {
+        void activityManagerOnUserStopped(@UserIdInt int userId) {
             LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
         }
 
-        void systemServiceManagerCleanupUser(int userId) {
+        void systemServiceManagerCleanupUser(@UserIdInt int userId) {
             mService.mSystemServiceManager.cleanupUser(userId);
         }
 
@@ -2330,7 +2331,7 @@
             }
         }
 
-        void sendPreBootBroadcast(int userId, boolean quiet, final Runnable onFinish) {
+        void sendPreBootBroadcast(@UserIdInt int userId, boolean quiet, final Runnable onFinish) {
             new PreBootBroadcaster(mService, userId, null, quiet) {
                 @Override
                 public void onFinished() {
@@ -2339,7 +2340,7 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackage(int userId, String reason) {
+        void activityManagerForceStopPackage(@UserIdInt int userId, String reason) {
             synchronized (mService) {
                 mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
                         userId, reason);
@@ -2351,11 +2352,11 @@
             return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
         }
 
-        protected void startHomeActivity(int userId, String reason) {
+        protected void startHomeActivity(@UserIdInt int userId, String reason) {
             mService.mAtmInternal.startHomeActivity(userId, reason);
         }
 
-        void startUserWidgets(int userId) {
+        void startUserWidgets(@UserIdInt int userId) {
             AppWidgetManagerInternal awm = LocalServices.getService(AppWidgetManagerInternal.class);
             if (awm != null) {
                 // Out of band, because this is called during a sequence with
@@ -2370,13 +2371,13 @@
             mService.mAtmInternal.updateUserConfiguration();
         }
 
-        void clearBroadcastQueueForUser(int userId) {
+        void clearBroadcastQueueForUser(@UserIdInt int userId) {
             synchronized (mService) {
                 mService.clearBroadcastQueueForUserLocked(userId);
             }
         }
 
-        void loadUserRecents(int userId) {
+        void loadUserRecents(@UserIdInt int userId) {
             mService.mAtmInternal.loadRecentTasksForUser(userId);
         }
 
@@ -2384,7 +2385,7 @@
             mService.startPersistentApps(matchFlags);
         }
 
-        void installEncryptionUnawareProviders(int userId) {
+        void installEncryptionUnawareProviders(@UserIdInt int userId) {
             mService.installEncryptionUnawareProviders(userId);
         }
 
@@ -2417,11 +2418,11 @@
             }
         }
 
-        void stackSupervisorRemoveUser(int userId) {
+        void stackSupervisorRemoveUser(@UserIdInt int userId) {
             mService.mAtmInternal.removeUser(userId);
         }
 
-        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+        protected boolean stackSupervisorSwitchUser(@UserIdInt int userId, UserState uss) {
             return mService.mAtmInternal.switchUser(userId, uss);
         }
 
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index cc8e3f0..159e5b8 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -31,9 +31,11 @@
 import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
+import static android.app.AppOpsManager._NUM_OP;
 import static android.app.AppOpsManager.modeToName;
 import static android.app.AppOpsManager.opToName;
 import static android.app.AppOpsManager.resolveFirstUnrestrictedUidState;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -49,6 +51,7 @@
 import android.app.AppOpsManager.OpFlags;
 import android.app.AppOpsManagerInternal;
 import android.app.AppOpsManagerInternal.CheckOpsDelegate;
+import android.app.AsyncNotedAppOp;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -58,9 +61,10 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
-import android.media.AudioAttributes;
+import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -69,6 +73,7 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -86,6 +91,7 @@
 import android.util.KeyValueListParser;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -96,6 +102,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsAsyncNotedCallback;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsNotedCallback;
 import com.android.internal.app.IAppOpsService;
@@ -181,6 +188,8 @@
             OP_CAMERA,
     };
 
+    private static final int MAX_UNFORWARED_OPS = 10;
+
     Context mContext;
     final AtomicFile mFile;
     final Handler mHandler;
@@ -188,6 +197,29 @@
     private final AppOpsManagerInternalImpl mAppOpsManagerInternal
             = new AppOpsManagerInternalImpl();
 
+    /**
+     * Registered callbacks, called from {@link #noteAsyncOp}.
+     *
+     * <p>(package name, uid) -> callbacks
+     *
+     * @see #getAsyncNotedOpsKey(String, int)
+     */
+    @GuardedBy("this")
+    private final ArrayMap<Pair<String, Integer>, RemoteCallbackList<IAppOpsAsyncNotedCallback>>
+            mAsyncOpWatchers = new ArrayMap<>();
+
+    /**
+     * Async note-ops collected from {@link #noteAsyncOp} that have not been delivered to a
+     * callback yet.
+     *
+     * <p>(package name, uid) -> list&lt;ops&gt;
+     *
+     * @see #getAsyncNotedOpsKey(String, int)
+     */
+    @GuardedBy("this")
+    private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
+            mUnforwardedAsyncNotedOps = new ArrayMap<>();
+
     boolean mWriteScheduled;
     boolean mFastWriteScheduled;
     final Runnable mWriteRunner = new Runnable() {
@@ -328,7 +360,7 @@
 
         public int startNesting;
         public ArrayMap<String, Ops> pkgOps;
-        public SparseIntArray opModes;
+        private SparseIntArray opModes;
 
         // true indicates there is an interested observer, false there isn't but it has such an op
         public SparseBooleanArray foregroundOps;
@@ -350,6 +382,64 @@
                     && (pendingState == UID_STATE_CACHED));
         }
 
+        public int getOpModeCount() {
+            return opModes != null ? opModes.size() : 0;
+        }
+
+        public int getOpCodeAt(int index) {
+            return opModes.keyAt(index);
+        }
+
+        public boolean hasOpMode(int code) {
+            return opModes != null && opModes.indexOfKey(code) >= 0;
+        }
+
+        public int getOpMode(int code) {
+            return opModes.get(code);
+        }
+
+        public boolean putOpMode(int code, int mode) {
+            if (mode == AppOpsManager.opToDefaultMode(code)) {
+                return removeOpMode(code);
+            }
+            if (opModes == null) {
+                opModes = new SparseIntArray();
+            }
+            int index = opModes.indexOfKey(code);
+            if (index < 0) {
+                opModes.put(code, mode);
+                return true;
+            }
+            if (opModes.valueAt(index) == mode) {
+                return false;
+            }
+            opModes.setValueAt(index, mode);
+            return true;
+        }
+
+        public boolean removeOpMode(int code) {
+            if (opModes == null) {
+                return false;
+            }
+            int index = opModes.indexOfKey(code);
+            if (index < 0) {
+                return false;
+            }
+            opModes.removeAt(index);
+            if (opModes.size() == 0) {
+                opModes = null;
+            }
+            return true;
+        }
+
+        @Nullable
+        public SparseIntArray cloneOpModes() {
+            if (opModes == null) {
+                return null;
+            }
+            return opModes.clone();
+        }
+
         int evalMode(int op, int mode) {
             if (mode == AppOpsManager.MODE_FOREGROUND) {
                 return state <= AppOpsManager.resolveFirstUnrestrictedUidState(op)
@@ -377,14 +467,13 @@
         public void evalForegroundOps(SparseArray<ArraySet<ModeCallback>> watchers) {
             SparseBooleanArray which = null;
             hasForegroundWatchers = false;
-            if (opModes != null) {
-                for (int i = opModes.size() - 1; i >= 0; i--) {
-                    if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
-                        if (which == null) {
-                            which = new SparseBooleanArray();
-                        }
-                        evalForegroundWatchers(opModes.keyAt(i), watchers, which);
+            for (int i = getOpModeCount() - 1; i >= 0; i--) {
+                int code = getOpCodeAt(i);
+                if (getOpMode(code) == AppOpsManager.MODE_FOREGROUND) {
+                    if (which == null) {
+                        which = new SparseBooleanArray();
                     }
+                    evalForegroundWatchers(code, watchers, which);
                 }
             }
             if (pkgOps != null) {
@@ -519,6 +608,10 @@
 
         private void updateProxyState(long key, int proxyUid,
             @Nullable String proxyPackageName) {
+            if (proxyUid == Process.INVALID_UID) {
+                return;
+            }
+
             if (mProxyUids == null) {
                 mProxyUids = new LongSparseLongArray();
             }
@@ -540,7 +633,7 @@
     final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
     final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
-    final SparseArray<SparseArray<Restriction>> mAudioRestrictions = new SparseArray<>();
+    final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
 
     final class ModeCallback implements DeathRecipient {
         final IAppOpsCallback mCallback;
@@ -793,20 +886,20 @@
                 final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
                 final String[] changedPkgs = intent.getStringArrayExtra(
                         Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                ArraySet<ModeCallback> callbacks;
-                synchronized (AppOpsService.this) {
-                    callbacks = mOpModeWatchers.get(OP_PLAY_AUDIO);
-                    if (callbacks == null) {
-                        return;
+                for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    ArraySet<ModeCallback> callbacks;
+                    synchronized (AppOpsService.this) {
+                        callbacks = mOpModeWatchers.get(code);
+                        if (callbacks == null) {
+                            continue;
+                        }
+                        callbacks = new ArraySet<>(callbacks);
                     }
-                    callbacks = new ArraySet<>(callbacks);
-                }
-                for (int i = 0; i < changedUids.length; i++) {
-                    final int changedUid = changedUids[i];
-                    final String changedPkg = changedPkgs[i];
-                    // We trust packagemanager to insert matching uid and packageNames in the
-                    // extras
-                    for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+                    for (int i = 0; i < changedUids.length; i++) {
+                        final int changedUid = changedUids[i];
+                        final String changedPkg = changedPkgs[i];
+                        // We trust packagemanager to insert matching uid and packageNames in the
+                        // extras
                         notifyOpChanged(callbacks, code, changedUid, changedPkg);
                     }
                 }
@@ -1023,24 +1116,28 @@
         return resOps;
     }
 
-    private ArrayList<AppOpsManager.OpEntry> collectOps(SparseIntArray uidOps, int[] ops) {
-        if (uidOps == null) {
+    @Nullable
+    private ArrayList<AppOpsManager.OpEntry> collectOps(@NonNull UidState uidState,
+            @Nullable int[] ops) {
+        int opModeCount = uidState.getOpModeCount();
+        if (opModeCount == 0) {
             return null;
         }
         ArrayList<AppOpsManager.OpEntry> resOps = null;
         if (ops == null) {
             resOps = new ArrayList<>();
-            for (int j=0; j<uidOps.size(); j++) {
-                resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+            for (int i = 0; i < opModeCount; i++) {
+                int code = uidState.getOpCodeAt(i);
+                resOps.add(new OpEntry(code, uidState.getOpMode(code)));
             }
         } else {
-            for (int j=0; j<ops.length; j++) {
-                int index = uidOps.indexOfKey(ops[j]);
-                if (index >= 0) {
+            for (int i = 0; i < ops.length; i++) {
+                int code = ops[i];
+                if (uidState.hasOpMode(code)) {
                     if (resOps == null) {
                         resOps = new ArrayList<>();
                     }
-                    resOps.add(new OpEntry(uidOps.keyAt(j), uidOps.valueAt(j)));
+                    resOps.add(new OpEntry(code, uidState.getOpMode(code)));
                 }
             }
         }
@@ -1186,11 +1283,11 @@
             if (uidState == null) {
                 return null;
             }
-            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState.opModes, ops);
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(uidState, ops);
             if (resOps == null) {
                 return null;
             }
-            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
             AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
                     null, uidState.uid, resOps);
             res.add(resPackage);
@@ -1258,29 +1355,14 @@
                     return;
                 }
                 uidState = new UidState(uid);
-                uidState.opModes = new SparseIntArray();
-                uidState.opModes.put(code, mode);
+                uidState.putOpMode(code, mode);
                 mUidStates.put(uid, uidState);
                 scheduleWriteLocked();
-            } else if (uidState.opModes == null) {
-                if (mode != defaultMode) {
-                    uidState.opModes = new SparseIntArray();
-                    uidState.opModes.put(code, mode);
+            } else {
+                boolean changed = uidState.putOpMode(code, mode);
+                if (changed) {
                     scheduleWriteLocked();
                 }
-            } else {
-                if (uidState.opModes.indexOfKey(code) >= 0 && uidState.opModes.get(code) == mode) {
-                    return;
-                }
-                if (mode == defaultMode) {
-                    uidState.opModes.delete(code);
-                    if (uidState.opModes.size() <= 0) {
-                        uidState.opModes = null;
-                    }
-                } else {
-                    uidState.opModes.put(code, mode);
-                }
-                scheduleWriteLocked();
             }
             uidState.evalForegroundOps(mOpModeWatchers);
         }
@@ -1519,16 +1601,13 @@
             for (int i = mUidStates.size() - 1; i >= 0; i--) {
                 UidState uidState = mUidStates.valueAt(i);
 
-                SparseIntArray opModes = uidState.opModes;
-                if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
-                    final int uidOpCount = opModes.size();
-                    for (int j = uidOpCount - 1; j >= 0; j--) {
-                        final int code = opModes.keyAt(j);
+                if (uidState.uid == reqUid || reqUid == -1) {
+                    for (int opModeIndex = uidState.getOpModeCount() - 1; opModeIndex >= 0;
+                            opModeIndex--) {
+                        final int code = uidState.getOpCodeAt(opModeIndex);
+
                         if (AppOpsManager.opAllowsReset(code)) {
-                            opModes.removeAt(j);
-                            if (opModes.size() <= 0) {
-                                uidState.opModes = null;
-                            }
+                            uidState.removeOpMode(code);
                             for (String packageName : getPackagesForUid(uidState.uid)) {
                                 callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
                                         mOpModeWatchers.get(code));
@@ -1778,9 +1857,8 @@
             }
             code = AppOpsManager.opToSwitch(code);
             UidState uidState = getUidStateLocked(uid, false);
-            if (uidState != null && uidState.opModes != null
-                    && uidState.opModes.indexOfKey(code) >= 0) {
-                final int rawMode = uidState.opModes.get(code);
+            if (uidState != null && uidState.hasOpMode(code)) {
+                final int rawMode = uidState.getOpMode(code);
                 return raw ? rawMode : uidState.evalMode(code, rawMode);
             }
             Op op = getOpLocked(code, uid, packageName, false, false);
@@ -1805,60 +1883,43 @@
     }
 
     private int checkAudioOperationImpl(int code, int usage, int uid, String packageName) {
-        synchronized (this) {
-            final int mode = checkRestrictionLocked(code, usage, uid, packageName);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                return mode;
-            }
+        final int mode = mAudioRestrictionManager.checkAudioOperation(
+                code, usage, uid, packageName);
+        if (mode != AppOpsManager.MODE_ALLOWED) {
+            return mode;
         }
         return checkOperation(code, uid, packageName);
     }
 
-    private int checkRestrictionLocked(int code, int usage, int uid, String packageName) {
-        final SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
-        if (usageRestrictions != null) {
-            final Restriction r = usageRestrictions.get(usage);
-            if (r != null && !r.exceptionPackages.contains(packageName)) {
-                return r.mode;
-            }
-        }
-        return AppOpsManager.MODE_ALLOWED;
-    }
-
     @Override
     public void setAudioRestriction(int code, int usage, int uid, int mode,
             String[] exceptionPackages) {
         enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
-        synchronized (this) {
-            SparseArray<Restriction> usageRestrictions = mAudioRestrictions.get(code);
-            if (usageRestrictions == null) {
-                usageRestrictions = new SparseArray<Restriction>();
-                mAudioRestrictions.put(code, usageRestrictions);
-            }
-            usageRestrictions.remove(usage);
-            if (mode != AppOpsManager.MODE_ALLOWED) {
-                final Restriction r = new Restriction();
-                r.mode = mode;
-                if (exceptionPackages != null) {
-                    final int N = exceptionPackages.length;
-                    r.exceptionPackages = new ArraySet<String>(N);
-                    for (int i = 0; i < N; i++) {
-                        final String pkg = exceptionPackages[i];
-                        if (pkg != null) {
-                            r.exceptionPackages.add(pkg.trim());
-                        }
-                    }
-                }
-                usageRestrictions.put(usage, r);
-            }
-        }
+
+        mAudioRestrictionManager.setZenModeAudioRestriction(
+                code, usage, uid, mode, exceptionPackages);
 
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
     }
 
+
+    @Override
+    public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
+        enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+
+        mAudioRestrictionManager.setCameraAudioRestriction(mode);
+
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
+        mHandler.sendMessage(PooledLambda.obtainMessage(
+                AppOpsService::notifyWatchersOfChange, this,
+                AppOpsManager.OP_VIBRATE, UID_ANY));
+    }
+
     @Override
     public int checkPackage(int uid, String packageName) {
         Preconditions.checkNotNull(packageName);
@@ -1966,8 +2027,8 @@
             final int switchCode = AppOpsManager.opToSwitch(code);
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
-            if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+            if (uidState.hasOpMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
                             + switchCode + " (" + code + ") uid " + uid + " package "
@@ -2098,6 +2159,141 @@
     }
 
     @Override
+    public void noteAsyncOp(String callingPackageName, int uid, String packageName, int opCode,
+            String message) {
+        Preconditions.checkNotNull(message);
+        Preconditions.checkNotNull(packageName);
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        verifyIncomingUid(uid);
+        verifyIncomingOp(opCode);
+
+        int callingUid = Binder.getCallingUid();
+        long now = System.currentTimeMillis();
+
+        if (callingPackageName != null) {
+            verifyAndGetIsPrivileged(callingUid, callingPackageName);
+        }
+
+        long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+                RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+                AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid,
+                        callingPackageName, message, now);
+                final boolean[] wasNoteForwarded = {false};
+
+                if (callbacks != null) {
+                    callbacks.broadcast((cb) -> {
+                        try {
+                            cb.opNoted(asyncNotedOp);
+                            wasNoteForwarded[0] = true;
+                        } catch (RemoteException e) {
+                            Slog.e(TAG,
+                                    "Could not forward noteOp of " + opCode + " to " + packageName
+                                            + "/" + uid, e);
+                        }
+                    });
+                }
+
+                if (!wasNoteForwarded[0]) {
+                    ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key);
+                    if (unforwardedOps == null) {
+                        unforwardedOps = new ArrayList<>(1);
+                        mUnforwardedAsyncNotedOps.put(key, unforwardedOps);
+                    }
+
+                    unforwardedOps.add(asyncNotedOp);
+                    if (unforwardedOps.size() > MAX_UNFORWARED_OPS) {
+                        unforwardedOps.remove(0);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Compute a key to be used in {@link #mAsyncOpWatchers} and {@link #mUnforwardedAsyncNotedOps}
+     *
+     * @param packageName The package name of the app
+     * @param uid The uid of the app
+     *
+     * @return They key uniquely identifying the app
+     */
+    private @NonNull Pair<String, Integer> getAsyncNotedOpsKey(@NonNull String packageName,
+            int uid) {
+        return new Pair<>(packageName, uid);
+    }
+
+    @Override
+    public void startWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(callback);
+
+        int uid = Binder.getCallingUid();
+        Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+            if (callbacks == null) {
+                callbacks = new RemoteCallbackList<IAppOpsAsyncNotedCallback>() {
+                    @Override
+                    public void onCallbackDied(IAppOpsAsyncNotedCallback callback) {
+                        synchronized (AppOpsService.this) {
+                            if (getRegisteredCallbackCount() == 0) {
+                                mAsyncOpWatchers.remove(key);
+                            }
+                        }
+                    }
+                };
+                mAsyncOpWatchers.put(key, callbacks);
+            }
+
+            callbacks.register(callback);
+        }
+    }
+
+    @Override
+    public void stopWatchingAsyncNoted(String packageName, IAppOpsAsyncNotedCallback callback) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(callback);
+
+        int uid = Binder.getCallingUid();
+        Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
+            if (callbacks != null) {
+                callbacks.unregister(callback);
+                if (callbacks.getRegisteredCallbackCount() == 0) {
+                    mAsyncOpWatchers.remove(key);
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<AsyncNotedAppOp> extractAsyncOps(String packageName) {
+        Preconditions.checkNotNull(packageName);
+
+        int uid = Binder.getCallingUid();
+
+        verifyAndGetIsPrivileged(uid, packageName);
+
+        synchronized (this) {
+            return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
+        }
+    }
+
+    @Override
     public int startOperation(IBinder token, int code, int uid, String packageName,
             boolean startIfModeDefault) {
         verifyIncomingUid(uid);
@@ -2133,8 +2329,8 @@
             // If there is a non-default per UID policy (we set UID op mode only if
             // non-default) it takes over, otherwise use the per package policy.
             final int opCode = op.op;
-            if (uidState.opModes != null && uidState.opModes.indexOfKey(switchCode) >= 0) {
-                final int uidMode = uidState.evalMode(code, uidState.opModes.get(switchCode));
+            if (uidState.hasOpMode(switchCode)) {
+                final int uidMode = uidState.evalMode(code, uidState.getOpMode(switchCode));
                 if (uidMode != AppOpsManager.MODE_ALLOWED
                         && (!startIfModeDefault || uidMode != AppOpsManager.MODE_DEFAULT)) {
                     if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
@@ -2340,6 +2536,25 @@
         return AppOpsManager.permissionToOpCode(permission);
     }
 
+    @Override
+    public boolean shouldCollectNotes(int opCode) {
+        Preconditions.checkArgumentInRange(opCode, 0, _NUM_OP - 1, "opCode");
+
+        String perm = AppOpsManager.opToPermission(opCode);
+        if (perm == null) {
+            return false;
+        }
+
+        PermissionInfo permInfo;
+        try {
+            permInfo = mContext.getPackageManager().getPermissionInfo(perm, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+
+        return permInfo.getProtection() == PROTECTION_DANGEROUS;
+    }
+
     void finishOperationLocked(Op op, boolean finishNested) {
         final int opCode = op.op;
         final int uid = op.uidState.uid;
@@ -2434,9 +2649,8 @@
                                 || !callback.isWatchingUid(uidState.uid)) {
                             continue;
                         }
-                        boolean doAllPackages = uidState.opModes != null
-                                && uidState.opModes.indexOfKey(code) >= 0
-                                && uidState.opModes.get(code) == AppOpsManager.MODE_FOREGROUND;
+                        boolean doAllPackages = uidState.hasOpMode(code)
+                                && uidState.getOpMode(code) == AppOpsManager.MODE_FOREGROUND;
                         if (uidState.pkgOps != null) {
                             for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
                                 final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
@@ -2642,9 +2856,11 @@
     }
 
     private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+        if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+            return false;
+        }
         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
-        return ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)
-                && pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+        return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
     }
 
     private boolean isOpRestrictedLocked(int uid, int code, String packageName,
@@ -2757,12 +2973,9 @@
             if (uidState == null) {
                 continue;
             }
-            if (uidState.opModes != null) {
-                final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
-                if (idx >= 0) {
-                    uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                        uidState.opModes.valueAt(idx));
-                }
+            if (uidState.hasOpMode(AppOpsManager.OP_RUN_IN_BACKGROUND)) {
+                uidState.putOpMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.getOpMode(
+                        AppOpsManager.OP_RUN_IN_BACKGROUND));
             }
             if (uidState.pkgOps == null) {
                 continue;
@@ -2818,10 +3031,7 @@
                 final int code = Integer.parseInt(parser.getAttributeValue(null, "n"));
                 final int mode = Integer.parseInt(parser.getAttributeValue(null, "m"));
                 UidState uidState = getUidStateLocked(uid, true);
-                if (uidState.opModes == null) {
-                    uidState.opModes = new SparseIntArray();
-                }
-                uidState.opModes.put(code, mode);
+                uidState.putOpMode(code, mode);
             } else {
                 Slog.w(TAG, "Unknown element under <uid-ops>: "
                         + parser.getName());
@@ -2973,47 +3183,38 @@
                 out.startTag(null, "app-ops");
                 out.attribute(null, "v", String.valueOf(CURRENT_VERSION));
 
-                SparseArray<SparseIntArray> uidStatesClone;
+                final SparseArray<SparseIntArray> uidOpModes = new SparseArray<>();
                 synchronized (this) {
-                    uidStatesClone = new SparseArray<>(mUidStates.size());
-
-                    final int uidStateCount = mUidStates.size();
-                    for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                        UidState uidState = mUidStates.valueAt(uidStateNum);
-                        int uid = mUidStates.keyAt(uidStateNum);
-
-                        SparseIntArray opModes = uidState.opModes;
-                        if (opModes != null && opModes.size() > 0) {
-                            uidStatesClone.put(uid, new SparseIntArray(opModes.size()));
-
-                            final int opCount = opModes.size();
-                            for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                                uidStatesClone.get(uid).put(
-                                        opModes.keyAt(opCountNum),
-                                        opModes.valueAt(opCountNum));
-                            }
+                    final int uidStatesSize = mUidStates.size();
+                    for (int i = 0; i < uidStatesSize; i++) {
+                        final SparseIntArray opModes = mUidStates.valueAt(i).cloneOpModes();
+                        if (opModes != null) {
+                            final int uid = mUidStates.keyAt(i);
+                            uidOpModes.put(uid, opModes);
                         }
                     }
                 }
 
-                final int uidStateCount = uidStatesClone.size();
-                for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
-                    SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
-                    if (opModes != null && opModes.size() > 0) {
-                        out.startTag(null, "uid");
-                        out.attribute(null, "n",
-                                Integer.toString(uidStatesClone.keyAt(uidStateNum)));
-                        final int opCount = opModes.size();
-                        for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
-                            final int op = opModes.keyAt(opCountNum);
-                            final int mode = opModes.valueAt(opCountNum);
-                            out.startTag(null, "op");
-                            out.attribute(null, "n", Integer.toString(op));
-                            out.attribute(null, "m", Integer.toString(mode));
-                            out.endTag(null, "op");
-                        }
-                        out.endTag(null, "uid");
+                final int uidOpModesSize = uidOpModes.size();
+                for (int uidOpModesIndex = 0; uidOpModesIndex < uidOpModesSize; uidOpModesIndex++) {
+                    final int uid = uidOpModes.keyAt(uidOpModesIndex);
+                    final SparseIntArray opModes = uidOpModes.valueAt(uidOpModesIndex);
+
+                    out.startTag(null, "uid");
+                    out.attribute(null, "n", Integer.toString(uid));
+
+                    final int opModesSize = opModes.size();
+                    for (int opModesIndex = 0; opModesIndex < opModesSize; opModesIndex++) {
+                        final int code = opModes.keyAt(opModesIndex);
+                        final int mode = opModes.valueAt(opModesIndex);
+
+                        out.startTag(null, "op");
+                        out.attribute(null, "n", Integer.toString(code));
+                        out.attribute(null, "m", Integer.toString(mode));
+                        out.endTag(null, "op");
                     }
+
+                    out.endTag(null, "uid");
                 }
 
                 if (allOps != null) {
@@ -3949,52 +4150,31 @@
                     }
                 }
             }
-            if (mAudioRestrictions.size() > 0 && dumpOp < 0 && dumpPackage != null
-                    && dumpMode < 0 && !dumpWatchers && !dumpWatchers) {
-                boolean printedHeader = false;
-                for (int o=0; o<mAudioRestrictions.size(); o++) {
-                    final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
-                    final SparseArray<Restriction> restrictions = mAudioRestrictions.valueAt(o);
-                    for (int i=0; i<restrictions.size(); i++) {
-                        if (!printedHeader){
-                            pw.println("  Audio Restrictions:");
-                            printedHeader = true;
-                            needSep = true;
-                        }
-                        final int usage = restrictions.keyAt(i);
-                        pw.print("    "); pw.print(op);
-                        pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
-                        Restriction r = restrictions.valueAt(i);
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
-                        if (!r.exceptionPackages.isEmpty()) {
-                            pw.println("      Exceptions:");
-                            for (int j=0; j<r.exceptionPackages.size(); j++) {
-                                pw.print("        "); pw.println(r.exceptionPackages.valueAt(j));
-                            }
-                        }
-                    }
-                }
+            if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
+                    && dumpPackage != null && dumpMode < 0 && !dumpWatchers && !dumpWatchers) {
+                needSep = mAudioRestrictionManager.dump(pw) | needSep ;
             }
             if (needSep) {
                 pw.println();
             }
             for (int i=0; i<mUidStates.size(); i++) {
                 UidState uidState = mUidStates.valueAt(i);
-                final SparseIntArray opModes = uidState.opModes;
                 final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
 
                 if (dumpWatchers || dumpHistory) {
                     continue;
                 }
                 if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
-                    boolean hasOp = dumpOp < 0 || (uidState.opModes != null
-                            && uidState.opModes.indexOfKey(dumpOp) >= 0);
+                    boolean hasOp = dumpOp < 0 || uidState.hasOpMode(dumpOp);
                     boolean hasPackage = dumpPackage == null;
                     boolean hasMode = dumpMode < 0;
-                    if (!hasMode && opModes != null) {
-                        for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
-                            if (opModes.valueAt(opi) == dumpMode) {
+                    if (!hasMode) {
+                        int opModeCount = uidState.getOpModeCount();
+                        for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+                            int code = uidState.getOpCodeAt(opModeIndex);
+                            if (uidState.getOpMode(code) == dumpMode) {
                                 hasMode = true;
+                                break;
                             }
                         }
                     }
@@ -4061,20 +4241,18 @@
                 }
                 needSep = true;
 
-                if (opModes != null) {
-                    final int opModeCount = opModes.size();
-                    for (int j = 0; j < opModeCount; j++) {
-                        final int code = opModes.keyAt(j);
-                        final int mode = opModes.valueAt(j);
-                        if (dumpOp >= 0 && dumpOp != code) {
-                            continue;
-                        }
-                        if (dumpMode >= 0 && dumpMode != mode) {
-                            continue;
-                        }
-                        pw.print("      "); pw.print(AppOpsManager.opToName(code));
-                        pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
+                final int opModeCount = uidState.getOpModeCount();
+                for (int opModeIndex = 0; opModeIndex < opModeCount; opModeIndex++) {
+                    final int code = uidState.getOpCodeAt(opModeIndex);
+                    final int mode = uidState.getOpMode(code);
+                    if (dumpOp >= 0 && dumpOp != code) {
+                        continue;
                     }
+                    if (dumpMode >= 0 && dumpMode != mode) {
+                        continue;
+                    }
+                    pw.print("      "); pw.print(AppOpsManager.opToName(code));
+                    pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
                 }
 
                 if (pkgOps == null) {
@@ -4224,12 +4402,6 @@
         }
     }
 
-    private static final class Restriction {
-        private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
-        int mode;
-        ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
-    }
-
     @Override
     public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
         checkSystemUid("setUserRestrictions");
diff --git a/services/core/java/com/android/server/appop/AudioRestrictionManager.java b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
new file mode 100644
index 0000000..be87037
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AudioRestrictionManager.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appop;
+
+import android.app.AppOpsManager;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
+import android.media.AudioAttributes;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import java.io.PrintWriter;
+
+/**
+ * AudioRestrictionManager host all audio restriction related logic and states for AppOpsService.
+ */
+public class AudioRestrictionManager {
+    static final String TAG = "AudioRestriction";
+
+    // Audio restrictions coming from Zen mode API
+    final SparseArray<SparseArray<Restriction>> mZenModeAudioRestrictions = new SparseArray<>();
+    // Audio restrictions coming from Camera2 API
+    @CAMERA_AUDIO_RESTRICTION int mCameraAudioRestriction = CameraDevice.AUDIO_RESTRICTION_NONE;
+    // Predefined <code, usages> camera audio restriction settings
+    static final SparseArray<SparseBooleanArray> CAMERA_AUDIO_RESTRICTIONS;
+
+    static {
+        SparseBooleanArray audioMutedUsages = new SparseBooleanArray();
+        SparseBooleanArray vibrationMutedUsages = new SparseBooleanArray();
+        for (int usage : AudioAttributes.SDK_USAGES) {
+            final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
+            if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_NOTIFICATION ||
+                    suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL ||
+                    suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
+                audioMutedUsages.append(usage, true);
+                vibrationMutedUsages.append(usage, true);
+            } else if (suppressionBehavior != AudioAttributes.SUPPRESSIBLE_MEDIA &&
+                    suppressionBehavior != AudioAttributes.SUPPRESSIBLE_SYSTEM &&
+                    suppressionBehavior != AudioAttributes.SUPPRESSIBLE_NEVER) {
+                Slog.e(TAG, "Unknown audio suppression behavior" + suppressionBehavior);
+            }
+        }
+        CAMERA_AUDIO_RESTRICTIONS = new SparseArray<>();
+        CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_PLAY_AUDIO, audioMutedUsages);
+        CAMERA_AUDIO_RESTRICTIONS.append(AppOpsManager.OP_VIBRATE, vibrationMutedUsages);
+    }
+
+    private static final class Restriction {
+        private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
+        int mode;
+        ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
+    }
+
+    public int checkAudioOperation(int code, int usage, int uid, String packageName) {
+        synchronized (this) {
+            // Check for camera audio restrictions
+            if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
+                if (code == AppOpsManager.OP_VIBRATE || (code == AppOpsManager.OP_PLAY_AUDIO &&
+                        mCameraAudioRestriction ==
+                                CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND)) {
+                    final SparseBooleanArray mutedUsages = CAMERA_AUDIO_RESTRICTIONS.get(code);
+                    if (mutedUsages != null) {
+                        if (mutedUsages.get(usage)) {
+                            return AppOpsManager.MODE_IGNORED;
+                        }
+                    }
+                }
+            }
+
+            final int mode = checkZenModeRestrictionLocked(code, usage, uid, packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                return mode;
+            }
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    private int checkZenModeRestrictionLocked(int code, int usage, int uid, String packageName) {
+        final SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
+        if (usageRestrictions != null) {
+            final Restriction r = usageRestrictions.get(usage);
+            if (r != null && !r.exceptionPackages.contains(packageName)) {
+                return r.mode;
+            }
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    public void setZenModeAudioRestriction(int code, int usage, int uid, int mode,
+            String[] exceptionPackages) {
+        synchronized (this) {
+            SparseArray<Restriction> usageRestrictions = mZenModeAudioRestrictions.get(code);
+            if (usageRestrictions == null) {
+                usageRestrictions = new SparseArray<Restriction>();
+                mZenModeAudioRestrictions.put(code, usageRestrictions);
+            }
+            usageRestrictions.remove(usage);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                final Restriction r = new Restriction();
+                r.mode = mode;
+                if (exceptionPackages != null) {
+                    final int N = exceptionPackages.length;
+                    r.exceptionPackages = new ArraySet<String>(N);
+                    for (int i = 0; i < N; i++) {
+                        final String pkg = exceptionPackages[i];
+                        if (pkg != null) {
+                            r.exceptionPackages.add(pkg.trim());
+                        }
+                    }
+                }
+                usageRestrictions.put(usage, r);
+            }
+        }
+    }
+
+    public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
+        synchronized (this) {
+            mCameraAudioRestriction = mode;
+        }
+    }
+
+    public boolean hasActiveRestrictions() {
+        boolean hasActiveRestrictions = false;
+        synchronized (this) {
+            hasActiveRestrictions = (mZenModeAudioRestrictions.size() > 0 ||
+                mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE);
+        }
+        return hasActiveRestrictions;
+    }
+
+    // return: needSep used by AppOpsService#dump
+    public boolean dump(PrintWriter pw) {
+        boolean printedHeader = false;
+        boolean needSep = hasActiveRestrictions();
+
+        synchronized (this) {
+            for (int o = 0; o < mZenModeAudioRestrictions.size(); o++) {
+                final String op = AppOpsManager.opToName(mZenModeAudioRestrictions.keyAt(o));
+                final SparseArray<Restriction> restrictions = mZenModeAudioRestrictions.valueAt(o);
+                for (int i = 0; i < restrictions.size(); i++) {
+                    if (!printedHeader){
+                        pw.println("  Zen Mode Audio Restrictions:");
+                        printedHeader = true;
+
+                    }
+                    final int usage = restrictions.keyAt(i);
+                    pw.print("    "); pw.print(op);
+                    pw.print(" usage="); pw.print(AudioAttributes.usageToString(usage));
+                    Restriction r = restrictions.valueAt(i);
+                    pw.print(": mode="); pw.println(AppOpsManager.modeToName(r.mode));
+                    if (!r.exceptionPackages.isEmpty()) {
+                        pw.println("      Exceptions:");
+                        for (int j = 0; j < r.exceptionPackages.size(); j++) {
+                            pw.print("        "); pw.println(r.exceptionPackages.valueAt(j));
+                        }
+                    }
+                }
+            }
+            if (mCameraAudioRestriction != CameraDevice.AUDIO_RESTRICTION_NONE) {
+                pw.println("  Camera Audio Restriction Mode: " +
+                        cameraRestrictionModeToName(mCameraAudioRestriction));
+            }
+        }
+        return needSep;
+    }
+
+    private static String cameraRestrictionModeToName(@CAMERA_AUDIO_RESTRICTION int mode) {
+        switch (mode) {
+            case CameraDevice.AUDIO_RESTRICTION_NONE:
+                return "None";
+            case CameraDevice.AUDIO_RESTRICTION_VIBRATION:
+                return "MuteVibration";
+            case CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND:
+                return "MuteVibrationAndSound";
+            default:
+                return "Unknown";
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index ace0a7d..5983785 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -28,6 +28,7 @@
 import android.content.ContentResolver;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
@@ -36,6 +37,7 @@
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.LongSparseArray;
@@ -44,6 +46,7 @@
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.AtomicDirectory;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
@@ -272,6 +275,10 @@
 
     void dump(String prefix, PrintWriter pw, int filterUid,
               String filterPackage, int filterOp) {
+        if (!isApiEnabled()) {
+            return;
+        }
+
         synchronized (mOnDiskLock) {
             synchronized (mInMemoryLock) {
                 pw.println();
@@ -324,6 +331,11 @@
     void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
             @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
             @OpFlags int flags, @NonNull RemoteCallback callback) {
+        if (!isApiEnabled()) {
+            callback.sendResult(new Bundle());
+            return;
+        }
+
         synchronized (mOnDiskLock) {
             synchronized (mInMemoryLock) {
                 if (!isPersistenceInitializedMLocked()) {
@@ -344,6 +356,11 @@
     void getHistoricalOps(int uid, @NonNull String packageName,
             @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
             @OpFlags int flags, @NonNull RemoteCallback callback) {
+        if (!isApiEnabled()) {
+            callback.sendResult(new Bundle());
+            return;
+        }
+
         final long currentTimeMillis = System.currentTimeMillis();
         if (endTimeMillis == Long.MAX_VALUE) {
             endTimeMillis = currentTimeMillis;
@@ -681,6 +698,12 @@
         }
     }
 
+    private static boolean isApiEnabled() {
+        return Binder.getCallingUid() == Process.myUid()
+                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+    }
+
     private static final class Persistence {
         private static final boolean DEBUG = false;
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6010b1dc..e8198b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -57,13 +57,6 @@
     private final @NonNull AudioService mAudioService;
     private final @NonNull Context mContext;
 
-    /** Forced device usage for communications sent to AudioSystem */
-    private int mForcedUseForComm;
-    /**
-     * Externally reported force device usage state returned by getters: always consistent
-     * with requests by setters */
-    private int mForcedUseForCommExt;
-
     // Manages all connected devices, only ever accessed on the message loop
     private final AudioDeviceInventory mDeviceInventory;
     // Manages notifications to BT service
@@ -71,24 +64,34 @@
 
 
     //-------------------------------------------------------------------
-    // we use a different lock than mDeviceStateLock so as not to create
-    // lock contention between enqueueing a message and handling them
-    private static final Object sLastDeviceConnectionMsgTimeLock = new Object();
-    @GuardedBy("sLastDeviceConnectionMsgTimeLock")
+    /**
+     * Lock to guard:
+     * - any changes to the message queue: enqueueing or removing any message
+     * - state of A2DP enabled
+     * - force use for communication + SCO changes
+     */
+    private final Object mDeviceBrokerLock = new Object();
+
+    @GuardedBy("mDeviceBrokerLock")
     private static long sLastDeviceConnectMsgTime = 0;
 
-    // General lock to be taken whenever the state of the audio devices is to be checked or changed
-    private final Object mDeviceStateLock = new Object();
 
-    // Request to override default use of A2DP for media.
-    @GuardedBy("mDeviceStateLock")
+    /** Request to override default use of A2DP for media */
+    @GuardedBy("mDeviceBrokerLock")
     private boolean mBluetoothA2dpEnabled;
 
-    // lock always taken when accessing AudioService.mSetModeDeathHandlers
-    // TODO do not "share" the lock between AudioService and BtHelpr, see b/123769055
-    /*package*/ final Object mSetModeLock = new Object();
+    /** Forced device usage for communications sent to AudioSystem */
+    @GuardedBy("mDeviceBrokerLock")
+    private int mForcedUseForComm;
+    /**
+     * Externally reported force device usage state returned by getters: always consistent
+     * with requests by setters */
+    @GuardedBy("mDeviceBrokerLock")
+    private int mForcedUseForCommExt;
+
 
     //-------------------------------------------------------------------
+    /** Normal constructor used by AudioService */
     /*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service) {
         mContext = context;
         mAudioService = service;
@@ -127,36 +130,37 @@
     // All post* methods are asynchronous
 
     /*package*/ void onSystemReady() {
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                mBtHelper.onSystemReady();
-            }
-        }
+        mBtHelper.onSystemReady();
     }
 
     /*package*/ void onAudioServerDied() {
         // Restore forced usage for communications and record
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             AudioSystem.setParameters(
                     "BT_SCO=" + (mForcedUseForComm == AudioSystem.FORCE_BT_SCO ? "on" : "off"));
             onSetForceUse(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, "onAudioServerDied");
             onSetForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm, "onAudioServerDied");
+
+            // restore devices
+            sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
         }
-        // restore devices
-        sendMsgNoDelay(MSG_RESTORE_DEVICES, SENDMSG_REPLACE);
     }
 
     /*package*/ void setForceUse_Async(int useCase, int config, String eventSource) {
-        sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
-                useCase, config, eventSource);
+        synchronized (mDeviceBrokerLock) {
+            sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE,
+                    useCase, config, eventSource);
+        }
     }
 
     /*package*/ void toggleHdmiIfConnected_Async() {
-        sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_TOGGLE_HDMI, SENDMSG_QUEUE);
+        }
     }
 
     /*package*/ void disconnectAllBluetoothProfiles() {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             mBtHelper.disconnectAllBluetoothProfiles();
         }
     }
@@ -168,15 +172,11 @@
      * @param intent
      */
     /*package*/ void receiveBtEvent(@NonNull Intent intent) {
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                mBtHelper.receiveBtEvent(intent);
-            }
-        }
+        mBtHelper.receiveBtEvent(intent);
     }
 
     /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             if (mBluetoothA2dpEnabled == on) {
                 return;
             }
@@ -196,7 +196,7 @@
      * @return true if speakerphone state changed
      */
     /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             final boolean wasOn = isSpeakerphoneOn();
             if (on) {
                 if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
@@ -214,7 +214,7 @@
     }
 
     /*package*/ boolean isSpeakerphoneOn() {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
         }
     }
@@ -223,9 +223,7 @@
             @AudioService.ConnectionState int state, String address, String name,
             String caller) {
         //TODO move logging here just like in setBluetooth* methods
-        synchronized (mDeviceStateLock) {
-            mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
-        }
+        mDeviceInventory.setWiredDeviceConnectionState(type, state, address, name, caller);
     }
 
     private static final class BtDeviceConnectionInfo {
@@ -259,27 +257,24 @@
         final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
                 suppressNoisyIntent, a2dpVolume);
 
-        // when receiving a request to change the connection state of a device, this last request
-        // is the source of truth, so cancel all previous requests
-        removeAllA2dpConnectionEvents(device);
+        synchronized (mDeviceBrokerLock) {
+            // when receiving a request to change the connection state of a device, this last
+            // request is the source of truth, so cancel all previous requests
+            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                    device);
+            mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
+                    device);
+            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                    device);
+            mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                    device);
 
-        sendLMsgNoDelay(
-                state == BluetoothProfile.STATE_CONNECTED
-                        ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
-                        : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                SENDMSG_QUEUE, info);
-    }
-
-    /** remove all previously scheduled connection and disconnection events for the given device */
-    private void removeAllA2dpConnectionEvents(@NonNull BluetoothDevice device) {
-        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                device);
-        mBrokerHandler.removeMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
-                device);
-        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                device);
-        mBrokerHandler.removeMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                device);
+            sendLMsgNoDelay(
+                    state == BluetoothProfile.STATE_CONNECTED
+                            ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
+                            : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
+                    SENDMSG_QUEUE, info);
+        }
     }
 
     private static final class HearingAidDeviceConnectionInfo {
@@ -305,25 +300,27 @@
             boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
         final HearingAidDeviceConnectionInfo info = new HearingAidDeviceConnectionInfo(
                 device, state, suppressNoisyIntent, musicDevice, eventSource);
-        sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
+        }
     }
 
     // never called by system components
     /*package*/ void setBluetoothScoOnByApp(boolean on) {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
         }
     }
 
     /*package*/ boolean isBluetoothScoOnForApp() {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             return mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO;
         }
     }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
         //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             if (on) {
                 // do not accept SCO ON if SCO audio is not connected
                 if (!mBtHelper.isBluetoothScoOn()) {
@@ -346,58 +343,55 @@
     }
 
     /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
-        synchronized (mDeviceStateLock) {
-            return mDeviceInventory.startWatchingRoutes(observer);
-        }
+        return mDeviceInventory.startWatchingRoutes(observer);
+
     }
 
     /*package*/ AudioRoutesInfo getCurAudioRoutes() {
-        synchronized (mDeviceStateLock) {
-            return mDeviceInventory.getCurAudioRoutes();
-        }
+        return mDeviceInventory.getCurAudioRoutes();
     }
 
     /*package*/ boolean isAvrcpAbsoluteVolumeSupported() {
-        synchronized (mDeviceStateLock) {
-            return mBtHelper.isAvrcpAbsoluteVolumeSupported();
-        }
+        return mBtHelper.isAvrcpAbsoluteVolumeSupported();
     }
 
     /*package*/ boolean isBluetoothA2dpOn() {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ void postSetAvrcpAbsoluteVolumeIndex(int index) {
-        sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+        synchronized (mDeviceBrokerLock) {
+            sendIMsgNoDelay(MSG_I_SET_AVRCP_ABSOLUTE_VOLUME, SENDMSG_REPLACE, index);
+        }
     }
 
     /*package*/ void postSetHearingAidVolumeIndex(int index, int streamType) {
-        sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+        synchronized (mDeviceBrokerLock) {
+            sendIIMsgNoDelay(MSG_II_SET_HEARING_AID_VOLUME, SENDMSG_REPLACE, index, streamType);
+        }
     }
 
     /*package*/ void postDisconnectBluetoothSco(int exceptPid) {
-        sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+        synchronized (mDeviceBrokerLock) {
+            sendIMsgNoDelay(MSG_I_DISCONNECT_BT_SCO, SENDMSG_REPLACE, exceptPid);
+        }
     }
 
     /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+        }
     }
 
-    @GuardedBy("mSetModeLock")
     /*package*/ void startBluetoothScoForClient_Sync(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
-        synchronized (mDeviceStateLock) {
-            mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
-        }
+        mBtHelper.startBluetoothScoForClient(cb, scoAudioMode, eventSource);
     }
 
-    @GuardedBy("mSetModeLock")
     /*package*/ void stopBluetoothScoForClient_Sync(IBinder cb, @NonNull String eventSource) {
-        synchronized (mDeviceStateLock) {
-            mBtHelper.stopBluetoothScoForClient(cb, eventSource);
-        }
+        mBtHelper.stopBluetoothScoForClient(cb, eventSource);
     }
 
     //---------------------------------------------------------------------
@@ -460,77 +454,109 @@
     //---------------------------------------------------------------------
     // Message handling on behalf of helper classes
     /*package*/ void postBroadcastScoConnectionState(int state) {
-        sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+        synchronized (mDeviceBrokerLock) {
+            sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
+        }
     }
 
     /*package*/ void postBroadcastBecomingNoisy() {
-        sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
+        }
     }
 
     /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
-                        ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
-                        : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                SENDMSG_QUEUE,
-                state, btDeviceInfo, delay);
+        synchronized (mDeviceBrokerLock) {
+            sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
+                            ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
+                            : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                    SENDMSG_QUEUE,
+                    state, btDeviceInfo, delay);
+        }
     }
 
     /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
-                state, btDeviceInfo, delay);
+        synchronized (mDeviceBrokerLock) {
+            sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
+                    state, btDeviceInfo, delay);
+        }
     }
 
     /*package*/ void postSetWiredDeviceConnectionState(
             AudioDeviceInventory.WiredDeviceConnectionState connectionState, int delay) {
-        sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE,
+                    connectionState, delay);
+        }
     }
 
     /*package*/ void postSetHearingAidConnectionState(
             @AudioService.BtProfileConnectionState int state,
             @NonNull BluetoothDevice device, int delay) {
-        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
-                state,
-                device,
-                delay);
+        synchronized (mDeviceBrokerLock) {
+            sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
+                    state,
+                    device,
+                    delay);
+        }
     }
 
     /*package*/ void postDisconnectA2dp() {
-        sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
+        }
     }
 
     /*package*/ void postDisconnectA2dpSink() {
-        sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
+        }
     }
 
     /*package*/ void postDisconnectHearingAid() {
-        sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
+        }
     }
 
     /*package*/ void postDisconnectHeadset() {
-        sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
+        }
     }
 
     /*package*/ void postBtA2dpProfileConnected(BluetoothA2dp a2dpProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
+        }
     }
 
     /*package*/ void postBtA2dpSinkProfileConnected(BluetoothProfile profile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
+        }
     }
 
     /*package*/ void postBtHeasetProfileConnected(BluetoothHeadset headsetProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE, headsetProfile);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE,
+                    headsetProfile);
+        }
     }
 
     /*package*/ void postBtHearingAidProfileConnected(BluetoothHearingAid hearingAidProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
-                hearingAidProfile);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
+                    hearingAidProfile);
+        }
     }
 
     /*package*/ void postScoClientDied(Object obj) {
-        sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_SCOCLIENT_DIED, SENDMSG_QUEUE, obj);
+        }
     }
 
     //---------------------------------------------------------------------
@@ -545,7 +571,7 @@
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).append(" src:").append(source).toString();
 
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             mBluetoothA2dpEnabled = on;
             mBrokerHandler.removeMessages(MSG_IIL_SET_FORCE_BT_A2DP_USE);
             onSetForceUse(
@@ -557,71 +583,85 @@
 
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
                                                        String deviceName) {
-        synchronized (mDeviceStateLock) {
-            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
-        }
+        return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
     }
 
     /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
         final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
-                btDeviceInfo);
+        synchronized (mDeviceBrokerLock) {
+            sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
+                    btDeviceInfo);
+        }
     }
 
     /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
-        sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+        synchronized (mDeviceBrokerLock) {
+            sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
+        }
     }
 
     /*package*/ void handleCancelFailureToConnectToBtHeadsetService() {
-        mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+        synchronized (mDeviceBrokerLock) {
+            mBrokerHandler.removeMessages(MSG_BT_HEADSET_CNCT_FAILED);
+        }
     }
 
     /*package*/ void postReportNewRoutes() {
-        sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+        synchronized (mDeviceBrokerLock) {
+            sendMsgNoDelay(MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
+        }
     }
 
     /*package*/ void cancelA2dpDockTimeout() {
-        mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+        synchronized (mDeviceBrokerLock) {
+            mBrokerHandler.removeMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+        }
     }
 
+    // FIXME: used by?
     /*package*/ void postA2dpActiveDeviceChange(
                     @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+        synchronized (mDeviceBrokerLock) {
+            sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
+        }
     }
 
     /*package*/ boolean hasScheduledA2dpDockTimeout() {
-        return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+        synchronized (mDeviceBrokerLock) {
+            return mBrokerHandler.hasMessages(MSG_IL_BTA2DP_DOCK_TIMEOUT);
+        }
     }
 
     // must be called synchronized on mConnectedDevices
     /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
-        return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
-                || mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                        new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
-    }
-
-    /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
-        sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
-    }
-
-    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
-        synchronized (mDeviceStateLock) {
-            mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+        synchronized (mDeviceBrokerLock) {
+            return (mBrokerHandler.hasMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
+                    new BtHelper.BluetoothA2dpDeviceInfo(btDevice))
+                    || mBrokerHandler.hasMessages(
+                            MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
+                            new BtHelper.BluetoothA2dpDeviceInfo(btDevice)));
         }
     }
 
+    /*package*/ void setA2dpDockTimeout(String address, int a2dpCodec, int delayMs) {
+        synchronized (mDeviceBrokerLock) {
+            sendILMsg(MSG_IL_BTA2DP_DOCK_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs);
+        }
+    }
+
+    /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) {
+        mBtHelper.setAvrcpAbsoluteVolumeSupported(supported);
+    }
+
     /*package*/ boolean getBluetoothA2dpEnabled() {
-        synchronized (mDeviceStateLock) {
+        synchronized (mDeviceBrokerLock) {
             return mBluetoothA2dpEnabled;
         }
     }
 
     /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
-        synchronized (mDeviceStateLock) {
-            return mBtHelper.getA2dpCodec(device);
-        }
+        return mBtHelper.getA2dpCodec(device);
     }
 
     /*package*/ void dump(PrintWriter pw, String prefix) {
@@ -709,68 +749,50 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_RESTORE_DEVICES:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onRestoreDevices();
-                        mBtHelper.onAudioServerDiedRestoreA2dp();
-                    }
+                    mDeviceInventory.onRestoreDevices();
+                    mBtHelper.onAudioServerDiedRestoreA2dp();
                     break;
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetWiredDeviceConnectionState(
-                                (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
-                    }
+                    mDeviceInventory.onSetWiredDeviceConnectionState(
+                            (AudioDeviceInventory.WiredDeviceConnectionState) msg.obj);
                     break;
                 case MSG_I_BROADCAST_BT_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onBroadcastScoConnectionState(msg.arg1);
-                    }
+                    mBtHelper.onBroadcastScoConnectionState(msg.arg1);
                     break;
                 case MSG_IIL_SET_FORCE_USE: // intended fall-through
                 case MSG_IIL_SET_FORCE_BT_A2DP_USE:
                     onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
                     break;
                 case MSG_REPORT_NEW_ROUTES:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onReportNewRoutes();
-                    }
+                    mDeviceInventory.onReportNewRoutes();
                     break;
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
                 case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetA2dpSinkConnectionState(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    }
+                    mDeviceInventory.onSetA2dpSinkConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
                     break;
                 case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetA2dpSourceConnectionState(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    }
+                    mDeviceInventory.onSetA2dpSourceConnectionState(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
                     break;
                 case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetHearingAidConnectionState(
-                                (BluetoothDevice) msg.obj, msg.arg1,
-                                mAudioService.getHearingAidStreamType());
-                    }
+                    mDeviceInventory.onSetHearingAidConnectionState(
+                            (BluetoothDevice) msg.obj, msg.arg1,
+                            mAudioService.getHearingAidStreamType());
                     break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            mBtHelper.resetBluetoothSco();
-                        }
-                    }
+                    mBtHelper.resetBluetoothSco();
                     break;
                 case MSG_IL_BTA2DP_DOCK_TIMEOUT:
                     // msg.obj  == address of BTA2DP device
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
-                    }
+                    mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1);
                     break;
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
                     final int a2dpCodec;
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
-                    synchronized (mDeviceStateLock) {
+                    synchronized (mDeviceBrokerLock) {
+                        // FIXME why isn't the codec coming with the request? lock shouldn't be
+                        // needed here
                         a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
                         mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
                                 new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
@@ -781,84 +803,48 @@
                     onSendBecomingNoisyIntent();
                     break;
                 case MSG_II_SET_HEARING_AID_VOLUME:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
-                    }
+                    mBtHelper.setHearingAidVolume(msg.arg1, msg.arg2);
                     break;
                 case MSG_I_SET_AVRCP_ABSOLUTE_VOLUME:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
-                    }
+                    mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                     break;
                 case MSG_I_DISCONNECT_BT_SCO:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            mBtHelper.disconnectBluetoothSco(msg.arg1);
-                        }
-                    }
+                    mBtHelper.disconnectBluetoothSco(msg.arg1);
                     break;
                 case MSG_L_SCOCLIENT_DIED:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            mBtHelper.scoClientDied(msg.obj);
-                        }
-                    }
+                    mBtHelper.scoClientDied(msg.obj);
                     break;
                 case MSG_TOGGLE_HDMI:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onToggleHdmi();
-                    }
+                    mDeviceInventory.onToggleHdmi();
                     break;
                 case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
-                                 BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
-                    }
+                    mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                            (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
+                            BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
                     break;
                 case MSG_DISCONNECT_A2DP:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectA2dp();
-                    }
+                    mDeviceInventory.disconnectA2dp();
                     break;
                 case MSG_DISCONNECT_A2DP_SINK:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectA2dpSink();
-                    }
+                    mDeviceInventory.disconnectA2dpSink();
                     break;
                 case MSG_DISCONNECT_BT_HEARING_AID:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectHearingAid();
-                    }
+                    mDeviceInventory.disconnectHearingAid();
                     break;
                 case MSG_DISCONNECT_BT_HEADSET:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            mBtHelper.disconnectHeadset();
-                        }
-                    }
+                    mBtHelper.disconnectHeadset();
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
-                    }
+                    mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
-                    }
+                    mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
-                    }
+                    mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
                     break;
                 case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
-                        }
-                    }
+                    mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
                     break;
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
                 case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
@@ -871,11 +857,9 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy
                                     + " vol=" + info.mVolume)).printLog(TAG));
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
-                                info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
-                                AudioSystem.DEVICE_NONE, info.mVolume);
-                    }
+                    mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
+                            info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
+                            AudioSystem.DEVICE_NONE, info.mVolume);
                 } break;
                 case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: {
                     final HearingAidDeviceConnectionInfo info =
@@ -885,10 +869,8 @@
                                     + " addr=" + info.mDevice.getAddress()
                                     + " supprNoisy=" + info.mSupprNoisy
                                     + " src=" + info.mEventSource)).printLog(TAG));
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
-                                info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
-                    }
+                    mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
+                            info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
                 } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
@@ -973,46 +955,57 @@
     /** If the msg is already queued, queue this one and leave the old. */
     private static final int SENDMSG_QUEUE = 2;
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendMsg(int msg, int existingMsgPolicy, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, delay);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsg(int msg, int existingMsgPolicy, int arg, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, delay);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsg(int msg, int existingMsgPolicy, Object obj, int delay) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, delay);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsg(int msg, int existingMsgPolicy, int arg, int delay) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, delay);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendMsgNoDelay(int msg, int existingMsgPolicy) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, null, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendIMsgNoDelay(int msg, int existingMsgPolicy, int arg) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, null, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendIIMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, null, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendILMsgNoDelay(int msg, int existingMsgPolicy, int arg, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg, 0, obj, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendLMsgNoDelay(int msg, int existingMsgPolicy, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, 0, 0, obj, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) {
         sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0);
     }
 
+    @GuardedBy("mDeviceBrokerLock")
     private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj,
                             int delay) {
         if (existingMsgPolicy == SENDMSG_REPLACE) {
@@ -1031,31 +1024,29 @@
             Binder.restoreCallingIdentity(identity);
         }
 
-        synchronized (sLastDeviceConnectionMsgTimeLock) {
-            long time = SystemClock.uptimeMillis() + delay;
+        long time = SystemClock.uptimeMillis() + delay;
 
-            switch (msg) {
-                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-                case MSG_IL_BTA2DP_DOCK_TIMEOUT:
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    if (sLastDeviceConnectMsgTime >= time) {
-                        // add a little delay to make sure messages are ordered as expected
-                        time = sLastDeviceConnectMsgTime + 30;
-                    }
-                    sLastDeviceConnectMsgTime = time;
-                    break;
-                default:
-                    break;
-            }
-
-            mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
-                    time);
+        switch (msg) {
+            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
+            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
+            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
+            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
+            case MSG_IL_BTA2DP_DOCK_TIMEOUT:
+            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
+                if (sLastDeviceConnectMsgTime >= time) {
+                    // add a little delay to make sure messages are ordered as expected
+                    time = sLastDeviceConnectMsgTime + 30;
+                }
+                sLastDeviceConnectMsgTime = time;
+                break;
+            default:
+                break;
         }
+
+        mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj),
+                time);
     }
 
     //-------------------------------------------------------------
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5bc2261..075842b 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
@@ -469,12 +469,11 @@
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
-    // package-private so it can be accessed in AudioDeviceBroker.getSetModeDeathHandlers
-    //TODO candidate to be moved to separate class that handles synchronization
-    @GuardedBy("mDeviceBroker.mSetModeLock")
-    /*package*/ final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
+    private final ArrayList<SetModeDeathHandler> mSetModeDeathHandlers =
             new ArrayList<SetModeDeathHandler>();
 
+    private volatile int mCurrentModeOwnerPid = 0;
+
     // true if boot sequence has been completed
     private boolean mSystemReady;
     // true if Intent.ACTION_USER_SWITCHED has ever been received
@@ -845,7 +844,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 +860,6 @@
                     mFixedVolumeDevices &= ~AudioSystem.DEVICE_OUT_HDMI;
                     mFullVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
                 }
-                mHdmiCecSink = false;
                 mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient();
             }
         }
@@ -1113,8 +1116,7 @@
                 checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, caller);
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null && mHdmiPlaybackClient != null) {
-                        mHdmiCecSink = false;
-                        mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
+                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
                     }
                 }
             }
@@ -1124,7 +1126,7 @@
             if (isPlatformTelevision()) {
                 synchronized (mHdmiClientLock) {
                     if (mHdmiManager != null) {
-                        mHdmiCecSink = false;
+                        updateHdmiCecSinkLocked(mHdmiCecSink | false);
                     }
                 }
             }
@@ -1902,16 +1904,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 +1917,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 +2358,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);
@@ -3139,15 +3149,10 @@
      * @return 0 if nobody owns the mode
      */
     /*package*/ int getModeOwnerPid() {
-        int modeOwnerPid = 0;
-        try {
-            modeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-        } catch (Exception e) {
-            // nothing to do, modeOwnerPid is not modified
-        }
-        return modeOwnerPid;
+        return  mCurrentModeOwnerPid;
     }
 
+
     private class SetModeDeathHandler implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mPid;
@@ -3161,7 +3166,7 @@
         public void binderDied() {
             int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
-            synchronized (mDeviceBroker.mSetModeLock) {
+            synchronized (mSetModeDeathHandlers) {
                 Log.w(TAG, "setMode() client died");
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
@@ -3172,11 +3177,15 @@
                 } else {
                     newModeOwnerPid = setModeInt(AudioSystem.MODE_NORMAL, mCb, mPid, TAG);
                 }
-            }
-            // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-            // SCO connections not started by the application changing the mode when pid changes
-            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-                mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
+
+                if (newModeOwnerPid != oldModeOwnerPid) {
+                    mCurrentModeOwnerPid = newModeOwnerPid;
+                    // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
+                    // connections not started by the application changing the mode when pid changes
+                    if (newModeOwnerPid != 0) {
+                        mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
+                    }
+                }
             }
         }
 
@@ -3199,15 +3208,17 @@
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
-        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
+        if (DEBUG_MODE) {
+            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
+        }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
 
-        if ( (mode == AudioSystem.MODE_IN_CALL) &&
-                (mContext.checkCallingOrSelfPermission(
+        if ((mode == AudioSystem.MODE_IN_CALL)
+                && (mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_PHONE_STATE)
-                            != PackageManager.PERMISSION_GRANTED)) {
+                        != PackageManager.PERMISSION_GRANTED)) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
             return;
@@ -3219,7 +3230,7 @@
 
         int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
-        synchronized (mDeviceBroker.mSetModeLock) {
+        synchronized (mSetModeDeathHandlers) {
             if (!mSetModeDeathHandlers.isEmpty()) {
                 oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
             }
@@ -3227,17 +3238,21 @@
                 mode = mMode;
             }
             newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
-        }
-        // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-        // SCO connections not started by the application changing the mode when pid changes
-        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
-            mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
+
+            if (newModeOwnerPid != oldModeOwnerPid) {
+                mCurrentModeOwnerPid = newModeOwnerPid;
+                // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
+                // SCO connections not started by the application changing the mode when pid changes
+                if (newModeOwnerPid != 0) {
+                    mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid);
+                }
+            }
         }
     }
 
     // setModeInt() returns a valid PID if the audio mode was successfully set to
     // any mode other than NORMAL.
-    @GuardedBy("mDeviceBroker.mSetModeLock")
+    @GuardedBy("mSetModeDeathHandlers")
     private int setModeInt(int mode, IBinder cb, int pid, String caller) {
         if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller="
                 + caller + ")"); }
@@ -3576,9 +3591,7 @@
                 !mSystemReady) {
             return;
         }
-        synchronized (mDeviceBroker.mSetModeLock) {
-            mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
-        }
+        mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -3590,9 +3603,7 @@
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
                 .append(Binder.getCallingPid()).toString();
-        synchronized (mDeviceBroker.mSetModeLock) {
-            mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
-        }
+        mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
     }
 
 
@@ -4341,7 +4352,7 @@
 
     // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
     //  1 mScoclient OR mSafeMediaVolumeState
-    //  2   mSetModeLock
+    //  2   mSetModeDeathHandlers
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
     private class VolumeStreamState {
@@ -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/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 9f1a6bd..625b6b6 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -171,8 +171,6 @@
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onSystemReady() {
         mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
         resetBluetoothSco();
@@ -245,8 +243,6 @@
         return mapBluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void receiveBtEvent(Intent intent) {
         final String action = intent.getAction();
         if (action.equals(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED)) {
@@ -333,8 +329,6 @@
      *
      * @param exceptPid pid whose SCO connections through {@link AudioManager} should be kept
      */
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectBluetoothSco(int exceptPid) {
         checkScoAudioState();
         if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL) {
@@ -343,8 +337,6 @@
         clearAllScoClients(exceptPid, true);
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void startBluetoothScoForClient(IBinder cb, int scoAudioMode,
                 @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, true);
@@ -364,8 +356,6 @@
         Binder.restoreCallingIdentity(ident);
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void stopBluetoothScoForClient(IBinder cb,
             @NonNull String eventSource) {
         ScoClient client = getScoClient(cb, false);
@@ -423,8 +413,6 @@
         mDeviceBroker.postDisconnectHearingAid();
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void resetBluetoothSco() {
         clearAllScoClients(0, false);
         mScoAudioState = SCO_STATE_INACTIVE;
@@ -433,8 +421,6 @@
         mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void disconnectHeadset() {
         setBtScoActiveDevice(null);
         mBluetoothHeadset = null;
@@ -480,8 +466,6 @@
                 /*eventSource*/ "mBluetoothProfileServiceListener");
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) {
         // Discard timeout message
         mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -568,8 +552,6 @@
         return result;
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void setBtScoActiveDevice(BluetoothDevice btDevice) {
         Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
@@ -652,8 +634,6 @@
             };
 
     //----------------------------------------------------------------------
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void scoClientDied(Object obj) {
         final ScoClient client = (ScoClient) obj;
         Log.w(TAG, "SCO client died");
@@ -684,8 +664,6 @@
             mDeviceBroker.postScoClientDied(this);
         }
 
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void incCount(int scoAudioMode) {
             if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) {
@@ -705,8 +683,6 @@
             mStartcount++;
         }
 
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void decCount() {
             if (mStartcount == 0) {
@@ -726,8 +702,6 @@
             }
         }
 
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         void clearCount(boolean stopSco) {
             if (mStartcount != 0) {
@@ -764,8 +738,6 @@
             return count;
         }
 
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         private boolean requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
@@ -959,8 +931,6 @@
         return null;
     }
 
-    // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-    //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void clearAllScoClients(int exceptPid, boolean stopSco) {
         ScoClient savedClient = null;
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
new file mode 100644
index 0000000..0d34c53
--- /dev/null
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+    "presubmit": [
+        {
+            "name": "CtsMediaTestCases",
+            "options": [
+                {
+                    "include-filter": "android.media.cts.AudioManagerTest"
+                },
+                {
+                    "include-filter": "android.media.cts.AudioFocusTest"
+                }
+            ]
+        }
+    ]
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 4af3627..7302b98 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -981,10 +981,10 @@
         mStatusBarService = mInjector.getStatusBarService();
 
         // Cache the authenticators
-        for (int i = 0; i < FEATURE_ID.length; i++) {
-            if (hasFeature(FEATURE_ID[i])) {
+        for (int featureId : FEATURE_ID) {
+            if (hasFeature(featureId)) {
                 Authenticator authenticator =
-                        new Authenticator(FEATURE_ID[i], getAuthenticator(FEATURE_ID[i]));
+                        new Authenticator(featureId, getAuthenticator(featureId));
                 mAuthenticators.add(authenticator);
             }
         }
@@ -1005,8 +1005,6 @@
      * and the error containing one of the {@link BiometricConstants} errors.
      */
     private Pair<Integer, Integer> checkAndGetBiometricModality(int userId) {
-        int modality = TYPE_NONE;
-
         // No biometric features, send error
         if (mAuthenticators.isEmpty()) {
             return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT);
@@ -1022,10 +1020,11 @@
         boolean hasTemplatesEnrolled = false;
         boolean enabledForApps = false;
 
+        int modality = TYPE_NONE;
         int firstHwAvailable = TYPE_NONE;
-        for (int i = 0; i < mAuthenticators.size(); i++) {
-            modality = mAuthenticators.get(i).getType();
-            BiometricAuthenticator authenticator = mAuthenticators.get(i).getAuthenticator();
+        for (Authenticator authenticatorWrapper : mAuthenticators) {
+            modality = authenticatorWrapper.getType();
+            BiometricAuthenticator authenticator = authenticatorWrapper.getAuthenticator();
             if (authenticator.isHardwareDetected()) {
                 isHardwareDetected = true;
                 if (firstHwAvailable == TYPE_NONE) {
@@ -1036,9 +1035,6 @@
                 if (authenticator.hasEnrolledTemplates(userId)) {
                     hasTemplatesEnrolled = true;
                     if (isEnabledForApp(modality, userId)) {
-                        // TODO(b/110907543): When face settings (and other settings) have both a
-                        // user toggle as well as a work profile settings page, this needs to be
-                        // updated to reflect the correct setting.
                         enabledForApps = true;
                         break;
                     }
@@ -1555,7 +1551,7 @@
     }
 
     /**
-     * authenticate() (above) which is called from BiometricPrompt determines which
+     * handleAuthenticate() (above) which is called from BiometricPrompt determines which
      * modality/modalities to start authenticating with. authenticateInternal() should only be
      * used for:
      * 1) Preparing <Biometric>Services for authentication when BiometricPrompt#authenticate is,
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 8de2595..96af74a 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -36,6 +36,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -44,6 +45,9 @@
 import com.android.server.SystemService;
 import com.android.server.wm.WindowManagerInternal;
 
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -75,7 +79,7 @@
     private static final int MSG_SWITCH_USER = 1;
 
     private static final int RETRY_DELAY_TIME = 20; //ms
-    private static final int RETRY_TIMES = 30;
+    private static final int RETRY_TIMES = 60;
 
     // Maximum entries to keep in usage history before dumping out
     private static final int MAX_USAGE_HISTORY = 100;
@@ -102,6 +106,9 @@
 
     private final boolean mNotifyNfc;
 
+    private ScheduledThreadPoolExecutor mLogWriterService = new ScheduledThreadPoolExecutor(
+            /*corePoolSize*/ 1);
+
     /**
      * Structure to track camera usage
      */
@@ -204,6 +211,9 @@
 
         mNotifyNfc = SystemProperties.getInt(NFC_NOTIFICATION_PROP, 0) > 0;
         if (DEBUG) Slog.v(TAG, "Notify NFC behavior is " + (mNotifyNfc ? "active" : "disabled"));
+        // Don't keep any extra logging threads if not needed
+        mLogWriterService.setKeepAliveTime(1, TimeUnit.SECONDS);
+        mLogWriterService.allowCoreThreadTimeOut(true);
     }
 
     @Override
@@ -279,6 +289,51 @@
         }
     }
 
+    private class EventWriterTask implements Runnable {
+        private ArrayList<CameraUsageEvent> mEventList;
+        private static final long WRITER_SLEEP_MS = 100;
+
+        public EventWriterTask(ArrayList<CameraUsageEvent> eventList) {
+            mEventList = eventList;
+        }
+
+        @Override
+        public void run() {
+            if (mEventList != null) {
+                for (CameraUsageEvent event : mEventList) {
+                    logCameraUsageEvent(event);
+                    try {
+                        Thread.sleep(WRITER_SLEEP_MS);
+                    } catch (InterruptedException e) {}
+                }
+                mEventList.clear();
+            }
+        }
+
+        /**
+         * Write camera usage events to stats log.
+         * Package-private
+         */
+        private void logCameraUsageEvent(CameraUsageEvent e) {
+            int facing = StatsLog.CAMERA_ACTION_EVENT__FACING__UNKNOWN;
+            switch(e.mCameraFacing) {
+                case ICameraServiceProxy.CAMERA_FACING_BACK:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__BACK;
+                    break;
+                case ICameraServiceProxy.CAMERA_FACING_FRONT:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__FRONT;
+                    break;
+                case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
+                    facing = StatsLog.CAMERA_ACTION_EVENT__FACING__EXTERNAL;
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing);
+            }
+            StatsLog.write(StatsLog.CAMERA_ACTION_EVENT, e.getDuration(), e.mAPILevel,
+                    e.mClientName, facing);
+        }
+    }
+
     /**
      * Dump camera usage events to log.
      * Package-private
@@ -315,6 +370,10 @@
                         .setPackageName(e.mClientName);
                 mLogger.write(l);
             }
+
+            mLogWriterService.execute(new EventWriterTask(
+                        new ArrayList<CameraUsageEvent>(mCameraUsageHistory)));
+
             mCameraUsageHistory.clear();
         }
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index fc38735..81e507c 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.util.Slog;
+import android.util.StatsLog;
 
+import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 
@@ -34,23 +36,27 @@
     private static final String TAG = "Compatibility";
 
     private final Context mContext;
+    private final ChangeReporter mChangeReporter;
 
     public PlatformCompat(Context context) {
         mContext = context;
+        mChangeReporter = new ChangeReporter();
     }
 
     @Override
     public void reportChange(long changeId, ApplicationInfo appInfo) {
-        Slog.d(TAG, "Compat change reported: " + changeId + "; UID " + appInfo.uid);
-        // TODO log via StatsLog
+        reportChange(changeId, appInfo, StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__LOGGED);
     }
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
         if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
-            reportChange(changeId, appInfo);
+            reportChange(changeId, appInfo,
+                    StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
         }
+        reportChange(changeId, appInfo,
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__DISABLED);
         return false;
     }
 
@@ -59,4 +65,13 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
         CompatConfig.get().dumpConfig(pw);
     }
+
+    private void reportChange(long changeId, ApplicationInfo appInfo, int state) {
+        int uid = appInfo.uid;
+        //TODO(b/138374585): Implement rate limiting for the logs.
+        Slog.d(TAG, ChangeReporter.createLogString(uid, changeId, state));
+        mChangeReporter.reportChange(uid, changeId,
+                state, /* source */
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index faf01f9..7b3eae1 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2082,7 +2082,6 @@
                     }
                     out.write(0xFF);
                     out.write(0xFF);
-                    out.flush();
 
                     // Wait for End-of-File.
                     InputStream in = mSockets[i].getInputStream();
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 36e872a..4a62bc5 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -55,6 +55,7 @@
 import android.os.ShellCallback;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -64,6 +65,8 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BinderDeathDispatcher;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -89,6 +92,14 @@
     /** Do a WTF if a single observer is registered more than this times. */
     private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000;
 
+    /**
+     * Delay to apply to content change notifications dispatched to apps running
+     * in the background. This is used to help prevent stampeding when the user
+     * is performing CPU/RAM intensive foreground tasks, such as when capturing
+     * burst photos.
+     */
+    private static final long BACKGROUND_OBSERVER_DELAY = 10 * DateUtils.SECOND_IN_MILLIS;
+
     public static class Lifecycle extends SystemService {
         private ContentService mService;
 
@@ -426,28 +437,15 @@
                         flags, userHandle, calls);
             }
             final int numCalls = calls.size();
-            for (int i=0; i<numCalls; i++) {
-                ObserverCall oc = calls.get(i);
-                try {
-                    oc.mObserver.onChange(oc.mSelfChange, uri, userHandle);
-                    if (DEBUG) Slog.d(TAG, "Notified " + oc.mObserver + " of " + "update at "
-                            + uri);
-                } catch (RemoteException ex) {
-                    synchronized (mRootNode) {
-                        Log.w(TAG, "Found dead observer, removing");
-                        IBinder binder = oc.mObserver.asBinder();
-                        final ArrayList<ObserverNode.ObserverEntry> list
-                                = oc.mNode.mObservers;
-                        int numList = list.size();
-                        for (int j=0; j<numList; j++) {
-                            ObserverNode.ObserverEntry oe = list.get(j);
-                            if (oe.observer.asBinder() == binder) {
-                                list.remove(j);
-                                j--;
-                                numList--;
-                            }
-                        }
-                    }
+            for (int i = 0; i < numCalls; i++) {
+                // Immediately dispatch notifications to foreground apps that
+                // are important to the user; all other background observers are
+                // delayed to avoid stampeding
+                final ObserverCall oc = calls.get(i);
+                if (oc.mProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    oc.run();
+                } else {
+                    BackgroundThread.getHandler().postDelayed(oc, BACKGROUND_OBSERVER_DELAY);
                 }
             }
             if ((flags&ContentResolver.NOTIFY_SYNC_TO_NETWORK) != 0) {
@@ -486,23 +484,33 @@
                 UserHandle.getCallingUserId(), Build.VERSION_CODES.CUR_DEVELOPMENT, callingPackage);
     }
 
-    /**
-     * Hide this class since it is not part of api,
-     * but current unittest framework requires it to be public
-     * @hide
-     *
-     */
-    public static final class ObserverCall {
-        final ObserverNode mNode;
+    /** {@hide} */
+    @VisibleForTesting
+    public static final class ObserverCall implements Runnable {
         final IContentObserver mObserver;
         final boolean mSelfChange;
-        final int mObserverUserId;
+        final Uri mUri;
+        final int mUserId;
+        final int mProcState;
 
-        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange, int observerUserId) {
-            mNode = node;
+        ObserverCall(IContentObserver observer, boolean selfChange, Uri uri, int userId,
+                int procState) {
             mObserver = observer;
             mSelfChange = selfChange;
-            mObserverUserId = observerUserId;
+            mUri = uri;
+            mUserId = userId;
+            mProcState = procState;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mObserver.onChange(mSelfChange, mUri, mUserId);
+                if (DEBUG) Slog.d(TAG, "Notified " + mObserver + " of update at " + mUri);
+            } catch (RemoteException ignored) {
+                // We already have a death observer that will clean up if the
+                // remote process dies
+            }
         }
     }
 
@@ -1345,11 +1353,8 @@
         return ContentResolver.SYNC_EXEMPTION_NONE;
     }
 
-    /**
-     * Hide this class since it is not part of api,
-     * but current unittest framework requires it to be public
-     * @hide
-     */
+    /** {@hide} */
+    @VisibleForTesting
     public static final class ObserverNode {
         private class ObserverEntry implements IBinder.DeathRecipient {
             public final IContentObserver observer;
@@ -1546,7 +1551,7 @@
             return false;
         }
 
-        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+        private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer,
                                               boolean observerWantsSelfNotifications, int flags,
                                               int targetUserHandle, ArrayList<ObserverCall> calls) {
             int N = mObservers.size();
@@ -1588,8 +1593,10 @@
                     if (DEBUG) Slog.d(TAG, "Reporting to " + entry.observer + ": leaf=" + leaf
                             + " flags=" + Integer.toHexString(flags)
                             + " desc=" + entry.notifyForDescendants);
-                    calls.add(new ObserverCall(this, entry.observer, selfChange,
-                            UserHandle.getUserId(entry.uid)));
+                    final int procState = LocalServices.getService(ActivityManagerInternal.class)
+                            .getUidProcessState(entry.uid);
+                    calls.add(new ObserverCall(entry.observer, selfChange, uri,
+                            targetUserHandle, procState));
                 }
             }
         }
@@ -1605,14 +1612,14 @@
             if (index >= segmentCount) {
                 // This is the leaf node, notify all observers
                 if (DEBUG) Slog.d(TAG, "Collecting leaf observers @ #" + index + ", node " + mName);
-                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+                collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
                         flags, targetUserHandle, calls);
             } else if (index < segmentCount){
                 segment = getUriSegment(uri, index);
                 if (DEBUG) Slog.d(TAG, "Collecting non-leaf observers @ #" + index + " / "
                         + segment);
                 // Notify any observers at this level who are interested in descendants
-                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+                collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
                         flags, targetUserHandle, calls);
             }
 
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 58aadd1..1fc0db3 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -69,6 +69,7 @@
     private static final int MSG_ALLOWED_MODES_CHANGED = 1;
     private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
     private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
+    private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
 
     // Special ID used to indicate that given vote is to be applied globally, rather than to a
     // specific display.
@@ -440,23 +441,48 @@
                     mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
                             defaultPeakRefreshRate);
                     break;
+
+                case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
+                    int refreshRateInZone = msg.arg1;
+                    mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
+                            refreshRateInZone);
+                    break;
             }
         }
     }
 
     private static final class Vote {
-        // We split the app request into two priorities in case we can satisfy one desire without
-        // the other.
-        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
-        public static final int PRIORITY_APP_REQUEST_SIZE = 1;
-        public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
-        public static final int PRIORITY_LOW_BRIGHTNESS = 3;
-        public static final int PRIORITY_LOW_POWER_MODE = 4;
+        // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
+        // If the higher voters result is a range, it will fix the rate to a single choice.
+        // It's used to avoid rate switch in certain conditions.
+        public static final int PRIORITY_LOW_BRIGHTNESS = 0;
+
+        // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
+        // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
+        public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+
+        // We split the app request into different priorities in case we can satisfy one desire
+        // without the other.
+
+        // Application can specify preferred refresh rate with below attrs.
+        // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+        // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+        // System also forces some apps like blacklisted app to run at a lower refresh rate.
+        // @see android.R.array#config_highRefreshRateBlacklist
+        public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
+        public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+
+        // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
+        // of low priority voters. It votes [0, max(PEAK, MIN)]
+        public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+
+        // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
+        public static final int PRIORITY_LOW_POWER_MODE = 5;
 
         // Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
         // appropriate, as well as priorityToString.
 
-        public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
+        public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
         public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
 
         /**
@@ -500,12 +526,16 @@
 
         public static String priorityToString(int priority) {
             switch (priority) {
+                case PRIORITY_LOW_BRIGHTNESS:
+                    return "PRIORITY_LOW_BRIGHTNESS";
+                case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_REFRESH_RATE:
                     return "PRIORITY_APP_REQUEST_REFRESH_RATE";
                 case PRIORITY_APP_REQUEST_SIZE:
                     return "PRIORITY_APP_REQUEST_SIZE";
-                case PRIORITY_USER_SETTING_REFRESH_RATE:
-                    return "PRIORITY_USER_SETTING_REFRESH_RATE";
+                case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+                    return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
                 case PRIORITY_LOW_POWER_MODE:
                     return "PRIORITY_LOW_POWER_MODE";
                 default:
@@ -608,12 +638,11 @@
             float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
                     Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
 
-            if (peakRefreshRate < minRefreshRate) {
-                peakRefreshRate = minRefreshRate;
-            }
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+                    Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+            updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
+                    Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
 
-            Vote vote = Vote.forRefreshRates(minRefreshRate, peakRefreshRate);
-            updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
             mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
         }
 
@@ -655,6 +684,7 @@
                 refreshRateVote = null;
                 sizeVote = null;
             }
+
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
             updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
             return;
@@ -799,6 +829,8 @@
         private boolean mRefreshRateChangeable = false;
         private boolean mLowPowerModeEnabled = false;
 
+        private int mRefreshRateInZone;
+
         BrightnessObserver(Context context, Handler handler) {
             super(handler);
             mContext = context;
@@ -815,6 +847,7 @@
 
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
+
             // DeviceConfig is accessible after system ready.
             int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
             int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
@@ -824,6 +857,8 @@
                 mDisplayBrightnessThresholds = brightnessThresholds;
                 mAmbientBrightnessThresholds = ambientThresholds;
             }
+
+            mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
             restartObserver();
             mDeviceConfigDisplaySettings.startListening();
         }
@@ -863,8 +898,16 @@
             restartObserver();
         }
 
+        public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
+            if (refreshRate != mRefreshRateInZone) {
+                mRefreshRateInZone = refreshRate;
+                restartObserver();
+            }
+        }
+
         public void dumpLocked(PrintWriter pw) {
             pw.println("  BrightnessObserver");
+            pw.println("    mRefreshRateInZone: " + mRefreshRateInZone);
 
             for (int d: mDisplayBrightnessThresholds) {
                 pw.println("    mDisplayBrightnessThreshold: " + d);
@@ -950,6 +993,10 @@
          * to value changes.
          */
         private boolean checkShouldObserve(int[] a) {
+            if (mRefreshRateInZone <= 0) {
+                return false;
+            }
+
             for (int d: a) {
                 if (d >= 0) {
                     return true;
@@ -959,37 +1006,42 @@
             return false;
         }
 
+        private boolean isInsideZone(int brightness, float lux) {
+            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
+                int disp = mDisplayBrightnessThresholds[i];
+                int ambi = mAmbientBrightnessThresholds[i];
+
+                if (disp >= 0 && ambi >= 0) {
+                    if (brightness <= disp && mAmbientLux <= ambi) {
+                        return true;
+                    }
+                } else if (disp >= 0) {
+                    if (brightness <= disp) {
+                        return true;
+                    }
+                } else if (ambi >= 0) {
+                    if (mAmbientLux <= ambi) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
         private void onBrightnessChangedLocked() {
             int brightness = Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS, -1);
 
             Vote vote = null;
-            for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
-                int disp = mDisplayBrightnessThresholds[i];
-                int ambi = mAmbientBrightnessThresholds[i];
-
-                if (disp >= 0 && ambi >= 0) {
-                    if (brightness <= disp && mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (disp >= 0) {
-                    if (brightness <= disp) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                } else if (ambi >= 0) {
-                    if (mAmbientLux <= ambi) {
-                        vote = Vote.forRefreshRates(0f, 60f);
-                    }
-                }
-
-                if (vote != null) {
-                    break;
-                }
+            boolean insideZone = isInsideZone(brightness, mAmbientLux);
+            if (insideZone) {
+                vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
             }
 
             if (DEBUG) {
                 Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " +  mAmbientLux +
-                        (vote != null ? " 60hz only" : " no refresh rate limit"));
+                        ", Vote " + vote);
             }
             updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
         }
@@ -1104,7 +1156,6 @@
     }
 
     private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
-
         public DeviceConfigDisplaySettings() {
         }
 
@@ -1118,7 +1169,8 @@
          */
         public int[] getBrightnessThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_BRIGHTNESS_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1126,7 +1178,8 @@
          */
         public int[] getAmbientThresholds() {
             return getIntArrayProperty(
-                    DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_AMBIENT_THRESHOLDS);
+                    DisplayManager.DeviceConfig.
+                            KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
         }
 
         /*
@@ -1143,17 +1196,32 @@
             return defaultPeakRefreshRate;
         }
 
+        public int getRefreshRateInZone() {
+            int defaultRefreshRateInZone = mContext.getResources().getInteger(
+                    R.integer.config_defaultRefreshRateInZone);
+
+            int refreshRate = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                    DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
+                    defaultRefreshRateInZone);
+
+            return refreshRate;
+        }
+
         @Override
         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
             int[] brightnessThresholds = getBrightnessThresholds();
             int[] ambientThresholds = getAmbientThresholds();
             Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+            int refreshRateInZone = getRefreshRateInZone();
 
             mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
                     new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
                     .sendToTarget();
             mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
                     defaultPeakRefreshRate).sendToTarget();
+            mHandler.obtainMessage(MSG_REFRESH_RATE_IN_ZONE_CHANGED, refreshRateInZone,
+                    0).sendToTarget();
         }
 
         private int[] getIntArrayProperty(String prop) {
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 7fb5b19..0bf43b6 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -609,6 +609,9 @@
 
                 @Override
                 public void onAnimationEnd(Animator animator) {
+                    Slog.d(TAG, tintController.getClass().getSimpleName()
+                            + " Animation cancelled: " + mIsCancelled
+                            + " to matrix: " + TintController.matrixToString(to, 16));
                     if (!mIsCancelled) {
                         // Ensure final color matrix is set at the end of the animation. If the
                         // animation is cancelled then don't set the final color matrix so the new
@@ -1314,8 +1317,10 @@
          * Reset the CCT value for the display white balance transform to its default value.
          */
         public boolean resetDisplayWhiteBalanceColorTemperature() {
-            return setDisplayWhiteBalanceColorTemperature(getContext().getResources()
-                    .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault));
+            int temperatureDefault = getContext().getResources()
+                    .getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
+            Slog.d(TAG, "resetDisplayWhiteBalanceColorTemperature: " + temperatureDefault);
+            return setDisplayWhiteBalanceColorTemperature(temperatureDefault);
         }
 
         /**
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index d2c6cd9..3f1c222 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -149,8 +149,6 @@
             cct = mTemperatureMax;
         }
 
-        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct);
-
         synchronized (mLock) {
             mCurrentColorTemperature = cct;
 
@@ -185,6 +183,9 @@
             java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
             java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
         }
+
+        Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
+                + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
     }
 
     @Override
@@ -225,28 +226,6 @@
         }
     }
 
-    /**
-     * Format a given matrix into a string.
-     *
-     * @param matrix the matrix to format
-     * @param columns number of columns in the matrix
-     */
-    private String matrixToString(float[] matrix, int columns) {
-        if (matrix == null || columns <= 0) {
-            Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string");
-            return "";
-        }
-
-        final StringBuilder sb = new StringBuilder("");
-        for (int i = 0; i < matrix.length; i++) {
-            if (i % columns == 0) {
-                sb.append("\n      ");
-            }
-            sb.append(String.format("%9.6f", matrix[i]));
-        }
-        return sb.toString();
-    }
-
     private ColorSpace.Rgb makeRgbColorSpaceFromXYZ(float[] redGreenBlueXYZ, float[] whiteXYZ) {
         return new ColorSpace.Rgb(
                 "Display Color Space",
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index b291c64..8d8b9b2 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -18,6 +18,7 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.util.Slog;
 
 import java.io.PrintWriter;
 
@@ -95,4 +96,29 @@
      * Returns whether or not this transform type is available on this device.
      */
     public abstract boolean isAvailable(Context context);
+
+    /**
+     * Format a given matrix into a string.
+     *
+     * @param matrix the matrix to format
+     * @param columns number of columns in the matrix
+     */
+    static String matrixToString(float[] matrix, int columns) {
+        if (matrix == null || columns <= 0) {
+            Slog.e(ColorDisplayService.TAG, "Invalid arguments when formatting matrix to string,"
+                    + " matrix is null: " + (matrix == null)
+                    + " columns: " + columns);
+            return "";
+        }
+
+        final StringBuilder sb = new StringBuilder("");
+        for (int i = 0; i < matrix.length; i++) {
+            if (i % columns == 0) {
+                sb.append("\n      ");
+            }
+            sb.append(String.format("%9.6f", matrix[i]));
+        }
+        return sb.toString();
+    }
+
 }
diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
index 02ec10e..7b1f4c3 100644
--- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
+++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java
@@ -95,6 +95,11 @@
     // A piecewise linear relationship between high light brightness and high light bias.
     private Spline.LinearSpline mHighLightAmbientBrightnessToBiasSpline;
 
+    private float mLatestAmbientColorTemperature;
+    private float mLatestAmbientBrightness;
+    private float mLatestLowLightBias;
+    private float mLatestHighLightBias;
+
     /**
      * @param brightnessSensor
      *      The sensor used to detect changes in the ambient brightness.
@@ -348,6 +353,7 @@
     public void updateAmbientColorTemperature() {
         final long time = System.currentTimeMillis();
         float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time);
+        mLatestAmbientColorTemperature = ambientColorTemperature;
 
         if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) {
             ambientColorTemperature =
@@ -355,6 +361,7 @@
         }
 
         float ambientBrightness = mBrightnessFilter.getEstimate(time);
+        mLatestAmbientBrightness = ambientBrightness;
 
         if (ambientColorTemperature != -1.0f &&
                 mLowLightAmbientBrightnessToBiasSpline != null) {
@@ -362,6 +369,7 @@
             ambientColorTemperature =
                     bias * ambientColorTemperature + (1.0f - bias)
                     * mLowLightAmbientColorTemperature;
+            mLatestLowLightBias = bias;
         }
         if (ambientColorTemperature != -1.0f &&
                 mHighLightAmbientBrightnessToBiasSpline != null) {
@@ -369,6 +377,7 @@
             ambientColorTemperature =
                     (1.0f - bias) * ambientColorTemperature + bias
                     * mHighLightAmbientColorTemperature;
+            mLatestHighLightBias = bias;
         }
 
         if (mAmbientColorTemperatureOverride != -1.0f) {
@@ -426,6 +435,11 @@
         }
         mPendingAmbientColorTemperature = -1.0f;
         mAmbientColorTemperatureHistory.add(mAmbientColorTemperature);
+        Slog.d(TAG, "Display cct: " + mAmbientColorTemperature
+                + " Latest ambient cct: " + mLatestAmbientColorTemperature
+                + " Latest ambient lux: " + mLatestAmbientBrightness
+                + " Latest low light bias: " + mLatestLowLightBias
+                + " Latest high light bias: " + mLatestHighLightBias);
         mColorDisplayServiceInternal.setDisplayWhiteBalanceColorTemperature(
                 (int) mAmbientColorTemperature);
         mLastAmbientColorTemperature = mAmbientColorTemperature;
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
index 376a864..c2af29c 100644
--- a/services/core/java/com/android/server/firewall/IntentFirewall.java
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -32,10 +32,12 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.Xml;
+
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -562,7 +564,7 @@
 
         @Override
         public void onEvent(int event, String path) {
-            if (path.endsWith(".xml")) {
+            if (path != null && path.endsWith(".xml")) {
                 // we wait 250ms before taking any action on an event, in order to dedup multiple
                 // events. E.g. a delete event followed by a create event followed by a subsequent
                 // write+close event
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index dacf372..9a85f952 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1635,6 +1635,10 @@
         removeAction(SystemAudioAutoInitiationAction.class);
         removeAction(SystemAudioStatusAction.class);
         removeAction(VolumeControlAction.class);
+
+        if (!mService.isControlEnabled()) {
+            setSystemAudioMode(false);
+        }
     }
 
     @ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ccc0d4b..b7eca29 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1461,7 +1461,7 @@
     // This method should be called by LockPatternUtil only, all internal methods in this class
     // should call setLockCredentialInternal.
     @Override
-    public void setLockCredential(byte[] credential, int type,
+    public boolean setLockCredential(byte[] credential, int type,
             byte[] savedCredential, int requestedQuality, int userId,
             boolean allowUntrustedChange) {
 
@@ -1471,8 +1471,10 @@
         }
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
-            setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId,
-                    allowUntrustedChange, /* isLockTiedToParent= */ false);
+            if (!setLockCredentialInternal(credential, type, savedCredential, requestedQuality,
+                    userId, allowUntrustedChange, /* isLockTiedToParent= */ false)) {
+                return false;
+            }
             setSeparateProfileChallengeEnabledLocked(userId, true, null);
             notifyPasswordChanged(userId);
         }
@@ -1481,13 +1483,14 @@
             setDeviceUnlockedForUser(userId);
         }
         notifySeparateProfileChallengeChanged(userId);
+        return true;
     }
 
     /**
      * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
      *     credentials are being tied to its parent's credentials.
      */
-    private void setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
+    private boolean setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
             byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange,
             boolean isLockTiedToParent) {
         // Normalize savedCredential and credential such that empty string is always represented
@@ -1500,9 +1503,9 @@
         }
         synchronized (mSpManager) {
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
-                return;
+                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
+                        savedCredential, requestedQuality, userId, allowUntrustedChange,
+                        isLockTiedToParent);
             }
         }
 
@@ -1519,7 +1522,7 @@
             setUserPasswordMetrics(CREDENTIAL_TYPE_NONE, null, userId);
             sendCredentialsOnChangeIfRequired(
                     credentialType, credential, userId, isLockTiedToParent);
-            return;
+            return true;
         }
         if (credential == null) {
             throw new IllegalArgumentException("Null credential with mismatched credential type");
@@ -1552,37 +1555,38 @@
             if (shouldMigrateToSyntheticPasswordLocked(userId)) {
                 initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
                         currentHandle.type, requestedQuality, userId);
-                spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
-                return;
+                return spBasedSetLockCredentialInternalLocked(credential, credentialType,
+                        savedCredential, requestedQuality, userId, allowUntrustedChange,
+                        isLockTiedToParent);
             }
         }
         if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
         byte[] enrolledHandle = enrollCredential(currentHandle.hash, savedCredential, credential,
                 userId);
-        if (enrolledHandle != null) {
-            CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
-            mStorage.writeCredentialHash(willStore, userId);
-            // push new secret and auth token to vold
-            GateKeeperResponse gkResponse;
-            try {
-                gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
-                        credential);
-            } catch (RemoteException e) {
-                throw new IllegalStateException("Failed to verify current credential", e);
-            }
-            setUserKeyProtection(userId, credential, convertResponse(gkResponse));
-            fixateNewestUserKeyAuth(userId);
-            // Refresh the auth token
-            doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
-                    null /* progressCallback */);
-            synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            sendCredentialsOnChangeIfRequired(
-                    credentialType, credential, userId, isLockTiedToParent);
-        } else {
-            throw new IllegalStateException(String.format("Failed to enroll %s",
+        if (enrolledHandle == null) {
+            Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
                     credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+            return false;
         }
+        CredentialHash willStore = CredentialHash.create(enrolledHandle, credentialType);
+        mStorage.writeCredentialHash(willStore, userId);
+        // push new secret and auth token to vold
+        GateKeeperResponse gkResponse;
+        try {
+            gkResponse = getGateKeeperService().verifyChallenge(userId, 0, willStore.hash,
+                    credential);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Failed to verify current credential", e);
+        }
+        setUserKeyProtection(userId, credential, convertResponse(gkResponse));
+        fixateNewestUserKeyAuth(userId);
+        // Refresh the auth token
+        doVerifyCredential(credential, credentialType, CHALLENGE_FROM_CALLER, 0, userId,
+                null /* progressCallback */);
+        synchronizeUnifiedWorkChallengeForProfiles(userId, null);
+        sendCredentialsOnChangeIfRequired(
+                credentialType, credential, userId, isLockTiedToParent);
+        return true;
     }
 
     private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
@@ -2711,7 +2715,7 @@
     }
 
     @GuardedBy("mSpManager")
-    private void spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
+    private boolean spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
             byte[] savedCredential, int requestedQuality, int userId,
             boolean allowUntrustedChange, boolean isLockTiedToParent) {
         if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
@@ -2736,8 +2740,9 @@
 
         // If existing credential is provided, the existing credential must match.
         if (savedCredential != null && auth == null) {
-            throw new IllegalStateException(String.format("Failed to enroll %s",
+            Slog.w(TAG, String.format("Failed to enroll %s: incorrect credential",
                     credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
+            return false;
         }
         boolean untrustedReset = false;
         if (auth != null) {
@@ -2757,7 +2762,8 @@
             }
             untrustedReset = true;
         } else /* responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ {
-            throw new IllegalStateException("Rate limit exceeded, so password was not changed.");
+            Slog.w(TAG, "Rate limit exceeded, so password was not changed.");
+            return false;
         }
 
         if (auth != null) {
@@ -2779,6 +2785,7 @@
             // synthetic password. That would invalidate existing escrow tokens though.
         }
         sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent);
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index fe12a94..5594614 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -47,6 +47,7 @@
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
@@ -325,23 +326,16 @@
             version = mCache.getVersion();
         }
 
-        RandomAccessFile raf = null;
         byte[] stored = null;
-        try {
-            raf = new RandomAccessFile(name, "r");
+        try (RandomAccessFile raf = new RandomAccessFile(name, "r")) {
             stored = new byte[(int) raf.length()];
             raf.readFully(stored, 0, stored.length);
             raf.close();
+        } catch (FileNotFoundException suppressed) {
+            // readFile() is also called by hasFile() to check the existence of files, in this
+            // case FileNotFoundException is expected.
         } catch (IOException e) {
             Slog.e(TAG, "Cannot read file " + e);
-        } finally {
-            if (raf != null) {
-                try {
-                    raf.close();
-                } catch (IOException e) {
-                    Slog.e(TAG, "Error closing file " + e);
-                }
-            }
         }
         mCache.putFileIfUnchanged(name, stored, version);
         return stored;
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index 358bdb9..e59bf16 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -41,10 +41,9 @@
             if (DBG) Slog.d(TAG, "missing config");
             return null;
         }
-        boolean userWantsBubbles = mConfig.bubblesEnabled(record.sbn.getUser());
         boolean appCanShowBubble =
                 mConfig.areBubblesAllowed(record.sbn.getPackageName(), record.sbn.getUid());
-        if (!userWantsBubbles || !appCanShowBubble) {
+        if (!mConfig.bubblesEnabled() || !appCanShowBubble) {
             record.setAllowBubble(false);
         } else {
             if (record.getChannel() != null) {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 64477a5..aed2927 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -38,6 +38,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -81,6 +84,39 @@
     }
 
     @Override
+    protected ArrayMap<Boolean, ArrayList<ComponentName>>
+            resetComponents(String packageName, int userId) {
+        resetPackage(packageName, userId);
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, new ArrayList<>(0));
+        changes.put(false, new ArrayList<>(0));
+        return changes;
+    }
+
+    /**
+     *  @return true if the passed package is enabled. false otherwise
+     */
+    boolean resetPackage(String packageName, int userId) {
+        boolean isAllowed = super.isPackageOrComponentAllowed(packageName, userId);
+        boolean isDefault = super.isDefaultComponentOrPackage(packageName);
+        if (!isAllowed && isDefault) {
+            setPackageOrComponentEnabled(packageName, userId, true, true);
+        }
+        if (isAllowed && !isDefault) {
+            setPackageOrComponentEnabled(packageName, userId, true, false);
+        }
+        return !isAllowed && isDefault;
+    }
+
+    @Override
+    void writeDefaults(XmlSerializer out) throws IOException {
+        synchronized (mDefaultsLock) {
+            String defaults = String.join(ENABLED_SERVICES_SEPARATOR, mDefaultPackages);
+            out.attribute(null, ATT_DEFAULTS, defaults);
+        }
+    }
+
+    @Override
     protected Config getConfig() {
         final Config c = new Config();
         c.caption = "condition provider";
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 4828bbf..48b0fd6 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -105,6 +105,7 @@
     static final String ATT_USER_ID = "user";
     static final String ATT_IS_PRIMARY = "primary";
     static final String ATT_VERSION = "version";
+    static final String ATT_DEFAULTS = "defaults";
 
     static final int DB_VERSION = 1;
 
@@ -128,6 +129,10 @@
      */
     private final ArrayList<Pair<ComponentName, Integer>> mServicesBound = new ArrayList<>();
     private final ArraySet<Pair<ComponentName, Integer>> mServicesRebinding = new ArraySet<>();
+    // we need these packages to be protected because classes that inherit from it need to see it
+    protected final Object mDefaultsLock = new Object();
+    protected final ArraySet<ComponentName> mDefaultComponents = new ArraySet<>();
+    protected final ArraySet<String> mDefaultPackages = new ArraySet<>();
 
     // lists the component names of all enabled (and therefore potentially connected)
     // app services for current profiles.
@@ -179,6 +184,112 @@
         }
     }
 
+    protected void addDefaultComponentOrPackage(String packageOrComponent) {
+        if (packageOrComponent != null) {
+            synchronized (mDefaultsLock) {
+                ComponentName cn = ComponentName.unflattenFromString(packageOrComponent);
+                if (cn == null) {
+                    mDefaultPackages.add(packageOrComponent);
+                } else {
+                    mDefaultPackages.add(cn.getPackageName());
+                    mDefaultComponents.add(cn);
+                }
+            }
+        }
+    }
+
+    boolean isDefaultComponentOrPackage(String packageOrComponent) {
+        synchronized (mDefaultsLock) {
+            ComponentName cn = ComponentName.unflattenFromString(packageOrComponent);
+            if (cn == null) {
+                return mDefaultPackages.contains(packageOrComponent);
+            } else {
+                return mDefaultComponents.contains(cn);
+            }
+        }
+    }
+
+    ArraySet<ComponentName> getDefaultComponents() {
+        synchronized (mDefaultsLock) {
+            return new ArraySet<>(mDefaultComponents);
+        }
+    }
+
+    ArraySet<String> getDefaultPackages() {
+        synchronized (mDefaultsLock) {
+            return new ArraySet<>(mDefaultPackages);
+        }
+    }
+
+    /**
+     * When resetting a package, we need to enable default components that belong to that packages
+     * we also need to disable components that are not default to return the managed service state
+     * to when a new android device is first turned on for that package.
+     *
+     * @param packageName package to reset.
+     * @param userId the android user id
+     * @return a list of components that were permitted
+     */
+    @NonNull
+    ArrayMap<Boolean, ArrayList<ComponentName>> resetComponents(String packageName, int userId) {
+        // components that we want to enable
+        ArrayList<ComponentName> componentsToEnable =
+                new ArrayList<>(mDefaultComponents.size());
+
+        // components that were removed
+        ArrayList<ComponentName> disabledComponents =
+                new ArrayList<>(mDefaultComponents.size());
+
+        // all components that are enabled now
+        ArraySet<ComponentName> enabledComponents =
+                new ArraySet<>(getAllowedComponents(userId));
+
+        boolean changed = false;
+
+        synchronized (mDefaultsLock) {
+            // record all components that are enabled but should not be by default
+            for (int i = 0; i < mDefaultComponents.size() && enabledComponents.size() > 0; i++) {
+                ComponentName currentDefault = mDefaultComponents.valueAt(i);
+                if (packageName.equals(currentDefault.getPackageName())
+                        && !enabledComponents.contains(currentDefault)) {
+                    componentsToEnable.add(currentDefault);
+                }
+            }
+            synchronized (mApproved) {
+                final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(
+                        userId);
+                if (approvedByType != null) {
+                    final int M = approvedByType.size();
+                    for (int j = 0; j < M; j++) {
+                        final ArraySet<String> approved = approvedByType.valueAt(j);
+                        for (int i = 0; i < enabledComponents.size(); i++) {
+                            ComponentName currentComponent = enabledComponents.valueAt(i);
+                            if (packageName.equals(currentComponent.getPackageName())
+                                    && !mDefaultComponents.contains(currentComponent)) {
+                                if (approved.remove(currentComponent.flattenToString())) {
+                                    disabledComponents.add(currentComponent);
+                                    changed = true;
+                                }
+                            }
+                        }
+                        for (int i = 0; i < componentsToEnable.size(); i++) {
+                            ComponentName candidate = componentsToEnable.get(i);
+                            changed |= approved.add(candidate.flattenToString());
+                        }
+                    }
+
+                }
+            }
+        }
+        if (changed) rebindServices(false, USER_ALL);
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, componentsToEnable);
+        changes.put(false, disabledComponents);
+
+        return changes;
+    }
+
     protected int getBindFlags() {
         return BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE | BIND_ALLOW_WHITELIST_MANAGEMENT;
     }
@@ -310,11 +421,24 @@
         }
     }
 
+    void writeDefaults(XmlSerializer out) throws IOException {
+        synchronized (mDefaultsLock) {
+            List<String> componentStrings = new ArrayList<>(mDefaultComponents.size());
+            for (int i = 0; i < mDefaultComponents.size(); i++) {
+                componentStrings.add(mDefaultComponents.valueAt(i).flattenToString());
+            }
+            String defaults = String.join(ENABLED_SERVICES_SEPARATOR, componentStrings);
+            out.attribute(null, ATT_DEFAULTS, defaults);
+        }
+    }
+
     public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
         out.startTag(null, getConfig().xmlTag);
 
         out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
 
+        writeDefaults(out);
+
         if (forBackup) {
             trimApprovedListsAccordingToInstalledServices(userId);
         }
@@ -378,6 +502,27 @@
         loadAllowedComponentsFromSettings();
     }
 
+    void readDefaults(XmlPullParser parser) {
+        String defaultComponents = XmlUtils.readStringAttribute(parser, ATT_DEFAULTS);
+        if (defaultComponents == null) {
+            return;
+        }
+        String[] components = defaultComponents.split(ENABLED_SERVICES_SEPARATOR);
+        synchronized (mDefaultsLock) {
+            for (int i = 0; i < components.length; i++) {
+                if (!TextUtils.isEmpty(components[i])) {
+                    ComponentName cn = ComponentName.unflattenFromString(components[i]);
+                    if (cn != null) {
+                        mDefaultPackages.add(cn.getPackageName());
+                        mDefaultComponents.add(cn);
+                    } else {
+                        mDefaultPackages.add(components[i]);
+                    }
+                }
+            }
+        }
+    }
+
     public void readXml(
             XmlPullParser parser,
             TriPredicate<String, Integer, String> allowedManagedServicePackages,
@@ -386,6 +531,7 @@
             throws XmlPullParserException, IOException {
         // read grants
         int type;
+        readDefaults(parser);
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             String tag = parser.getName();
             if (type == XmlPullParser.END_TAG
@@ -785,9 +931,9 @@
         return queryPackageForServices(packageName, 0, userId);
     }
 
-    protected Set<ComponentName> queryPackageForServices(String packageName, int extraFlags,
+    protected ArraySet<ComponentName> queryPackageForServices(String packageName, int extraFlags,
             int userId) {
-        Set<ComponentName> installed = new ArraySet<>();
+        ArraySet<ComponentName> installed = new ArraySet<>();
         final PackageManager pm = mContext.getPackageManager();
         Intent queryIntent = new Intent(mConfig.serviceInterface);
         if (!TextUtils.isEmpty(packageName)) {
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 9b9f4de..bc05154 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -129,6 +129,12 @@
             return -1 * Integer.compare(leftPriority, rightPriority);
         }
 
+        final boolean leftInterruptive = left.isInterruptive();
+        final boolean rightInterruptive = right.isInterruptive();
+        if (leftInterruptive != rightInterruptive) {
+            return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
+        }
+
         // then break ties by time, most recent first
         return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 30b2245..d480cb6e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -94,6 +94,7 @@
 import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
 import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.PreferencesHelper.DEFAULT_ALLOW_BUBBLE;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_CRITICAL;
 import static com.android.server.utils.PriorityDump.PRIORITY_ARG_NORMAL;
@@ -355,7 +356,6 @@
     private static final int REQUEST_CODE_TIMEOUT = 1;
     private static final String SCHEME_TIMEOUT = "timeout";
     private static final String EXTRA_KEY = "key";
-
     private IActivityManager mAm;
     private ActivityManager mActivityManager;
     private IPackageManager mPackageManager;
@@ -456,7 +456,7 @@
     private static final int MY_UID = Process.myUid();
     private static final int MY_PID = Process.myPid();
     private static final IBinder WHITELIST_TOKEN = new Binder();
-    private RankingHandler mRankingHandler;
+    protected RankingHandler mRankingHandler;
     private long mLastOverRateLogTime;
     private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
 
@@ -523,24 +523,21 @@
 
     }
 
-    protected void readDefaultApprovedServices(int userId) {
+
+    void loadDefaultApprovedServices(int userId) {
         String defaultListenerAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultListenerAccessPackages);
         if (defaultListenerAccess != null) {
-            for (String whitelisted :
-                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
-                // Gather all notification listener components for candidate pkgs.
-                Set<ComponentName> approvedListeners =
-                        mListeners.queryPackageForServices(whitelisted,
+            String[] listeners =
+                    defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < listeners.length; i++) {
+                ArraySet<ComponentName> approvedListeners =
+                        mListeners.queryPackageForServices(listeners[i],
                                 MATCH_DIRECT_BOOT_AWARE
                                         | MATCH_DIRECT_BOOT_UNAWARE, userId);
-                for (ComponentName cn : approvedListeners) {
-                    try {
-                        getBinderService().setNotificationListenerAccessGrantedForUser(cn,
-                                    userId, true);
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
+                for (int k = 0; k < approvedListeners.size(); k++) {
+                    ComponentName cn = approvedListeners.valueAt(k);
+                    mListeners.addDefaultComponentOrPackage(cn.flattenToString());
                 }
             }
         }
@@ -548,46 +545,86 @@
         String defaultDndAccess = getContext().getResources().getString(
                 com.android.internal.R.string.config_defaultDndAccessPackages);
         if (defaultDndAccess != null) {
-            for (String whitelisted :
-                    defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
-                try {
-                    getBinderService().setNotificationPolicyAccessGranted(whitelisted, true);
-                } catch (RemoteException e) {
-                    e.printStackTrace();
-                }
+            String[] dnds = defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < dnds.length; i++) {
+                mConditionProviders.addDefaultComponentOrPackage(dnds[i]);
             }
         }
 
+
+        ArraySet<String> assistants = new ArraySet<>();
+        String deviceAssistant = DeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE);
+        if (deviceAssistant != null) {
+            assistants.addAll(Arrays.asList(deviceAssistant.split(
+                    ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+        }
+        assistants.addAll(Arrays.asList(getContext().getResources().getString(
+                com.android.internal.R.string.config_defaultAssistantAccessComponent)
+                .split(ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+        for (int i = 0; i < assistants.size(); i++) {
+            String cnString = assistants.valueAt(i);
+            mAssistants.addDefaultComponentOrPackage(cnString);
+        }
+    }
+
+    protected void allowDefaultApprovedServices(int userId) {
+
+        ArraySet<ComponentName> defaultListeners = mListeners.getDefaultComponents();
+        for (int i = 0; i < defaultListeners.size(); i++) {
+            ComponentName cn = defaultListeners.valueAt(i);
+            allowNotificationListener(userId, cn);
+        }
+
+        ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
+        for (int i = 0; i < defaultDnds.size(); i++) {
+            allowDndPackage(defaultDnds.valueAt(i));
+        }
+
         setDefaultAssistantForUser(userId);
     }
 
     protected void setDefaultAssistantForUser(int userId) {
-        List<ComponentName> validAssistants = new ArrayList<>(
-                mAssistants.queryPackageForServices(
-                        null, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId));
-
-        List<String> candidateStrs = new ArrayList<>();
-        candidateStrs.add(DeviceConfig.getProperty(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE));
-        candidateStrs.add(getContext().getResources().getString(
-                com.android.internal.R.string.config_defaultAssistantAccessComponent));
-
-        for (String candidateStr : candidateStrs) {
-            if (TextUtils.isEmpty(candidateStr)) {
-                continue;
-            }
-            ComponentName candidate = ComponentName.unflattenFromString(candidateStr);
-            if (candidate != null && validAssistants.contains(candidate)) {
-                setNotificationAssistantAccessGrantedForUserInternal(candidate, userId, true);
-                Slog.d(TAG, String.format("Set default NAS to be %s in %d", candidateStr, userId));
-                return;
-            } else {
-                Slog.w(TAG, "Invalid default NAS config is found: " + candidateStr);
-            }
+        ArraySet<ComponentName> defaults = mAssistants.getDefaultComponents();
+        // We should have only one default assistant by default
+        // allowAssistant should execute once in practice
+        for (int i = 0; i < defaults.size(); i++) {
+            ComponentName cn = defaults.valueAt(i);
+            if (allowAssistant(userId, cn)) return;
         }
     }
 
+    private void allowDndPackage(String packageName) {
+        try {
+            getBinderService().setNotificationPolicyAccessGranted(packageName, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void allowNotificationListener(int userId, ComponentName cn) {
+
+        try {
+            getBinderService().setNotificationListenerAccessGrantedForUser(cn,
+                        userId, true);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean allowAssistant(int userId, ComponentName candidate) {
+        Set<ComponentName> validAssistants =
+                mAssistants.queryPackageForServices(
+                        null,
+                        MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
+        if (candidate != null && validAssistants.contains(candidate)) {
+            setNotificationAssistantAccessGrantedForUserInternal(candidate, userId, true);
+            return true;
+        }
+        return false;
+    }
+
     void readPolicyXml(InputStream stream, boolean forRestore, int userId)
             throws XmlPullParserException, NumberFormatException, IOException {
         final XmlPullParser parser = Xml.newPullParser();
@@ -655,7 +692,8 @@
             } catch (FileNotFoundException e) {
                 // No data yet
                 // Load default managed services approvals
-                readDefaultApprovedServices(USER_SYSTEM);
+                loadDefaultApprovedServices(USER_SYSTEM);
+                allowDefaultApprovedServices(USER_SYSTEM);
             } catch (IOException e) {
                 Log.wtf(TAG, "Unable to read notification policy", e);
             } catch (NumberFormatException e) {
@@ -925,7 +963,7 @@
                         () -> mAm.crashApplication(uid, initialPid, pkg, -1,
                             "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
                                 + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
-                                + message));
+                                + message, true /* force */));
             }
         }
 
@@ -1362,7 +1400,7 @@
                 if (userId != USER_NULL) {
                     mUserProfiles.updateCache(context);
                     if (!mUserProfiles.isManagedProfile(userId)) {
-                        readDefaultApprovedServices(userId);
+                        allowDefaultApprovedServices(userId);
                     }
                 }
             } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
@@ -1391,7 +1429,9 @@
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
-        private final Uri NOTIFICATION_BUBBLES_URI
+        private final Uri NOTIFICATION_BUBBLES_URI_GLOBAL
+                = Settings.Global.getUriFor(Settings.Global.NOTIFICATION_BUBBLES);
+        private final Uri NOTIFICATION_BUBBLES_URI_SECURE
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
         private final Uri NOTIFICATION_LIGHT_PULSE_URI
                 = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
@@ -1410,7 +1450,9 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
                     false, this, UserHandle.USER_ALL);
-            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_GLOBAL,
+                    false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI_SECURE,
                     false, this, UserHandle.USER_ALL);
             update(null);
         }
@@ -1437,9 +1479,41 @@
             if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
                 mPreferencesHelper.updateBadgingEnabled();
             }
-            if (uri == null || NOTIFICATION_BUBBLES_URI.equals(uri)) {
+            // In QPR we moved the setting to Global rather than Secure so that the setting
+            // applied to work profiles. Unfortunately we need to maintain both to pass CTS without
+            // a change to CTS outside of a normal letter release.
+            if (uri == null || NOTIFICATION_BUBBLES_URI_GLOBAL.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_GLOBAL);
                 mPreferencesHelper.updateBubblesEnabled();
             }
+            if (NOTIFICATION_BUBBLES_URI_SECURE.equals(uri)) {
+                syncBubbleSettings(resolver, NOTIFICATION_BUBBLES_URI_SECURE);
+            }
+        }
+
+        private void syncBubbleSettings(ContentResolver resolver, Uri settingToFollow) {
+            boolean followSecureSetting = settingToFollow.equals(NOTIFICATION_BUBBLES_URI_SECURE);
+
+            int secureSettingValue = Settings.Secure.getInt(resolver,
+                    Settings.Secure.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+            int globalSettingValue = Settings.Global.getInt(resolver,
+                    Settings.Global.NOTIFICATION_BUBBLES, DEFAULT_ALLOW_BUBBLE ? 1 : 0);
+
+            if (globalSettingValue == secureSettingValue) {
+                return;
+            }
+
+            if (followSecureSetting) {
+                // Global => secure
+                Settings.Global.putInt(resolver,
+                        Settings.Global.NOTIFICATION_BUBBLES,
+                        secureSettingValue);
+            } else {
+                // Secure => Global
+                Settings.Secure.putInt(resolver,
+                        Settings.Secure.NOTIFICATION_BADGING,
+                        globalSettingValue);
+            }
         }
     }
 
@@ -1513,10 +1587,12 @@
 
     @VisibleForTesting
     void clearNotifications() {
-        mEnqueuedNotifications.clear();
-        mNotificationList.clear();
-        mNotificationsByKey.clear();
-        mSummaryByGroupKey.clear();
+        synchronized (mNotificationList) {
+            mEnqueuedNotifications.clear();
+            mNotificationList.clear();
+            mNotificationsByKey.clear();
+            mSummaryByGroupKey.clear();
+        }
     }
 
     @VisibleForTesting
@@ -1568,11 +1644,6 @@
     void setPreferencesHelper(PreferencesHelper prefHelper) { mPreferencesHelper = prefHelper; }
 
     @VisibleForTesting
-    void setRankingHandler(RankingHandler rankingHandler) {
-        mRankingHandler = rankingHandler;
-    }
-
-    @VisibleForTesting
     void setZenHelper(ZenModeHelper zenHelper) {
         mZenModeHelper = zenHelper;
     }
@@ -1604,7 +1675,7 @@
 
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
-    void init(Looper looper, IPackageManager packageManager,
+    void init(Looper looper, RankingHandler rankingHandler, IPackageManager packageManager,
             PackageManager packageManagerClient,
             LightsManager lightsManager, NotificationListeners notificationListeners,
             NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
@@ -1638,7 +1709,6 @@
         mUm = userManager;
 
         mHandler = new WorkerHandler(looper);
-        mRankingThread.start();
         String[] extractorNames;
         try {
             extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors);
@@ -1647,7 +1717,7 @@
         }
         mUsageStats = usageStats;
         mMetricsLogger = new MetricsLogger();
-        mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
+        mRankingHandler = rankingHandler;
         mConditionProviders = conditionProviders;
         mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@@ -1708,7 +1778,6 @@
 
         mPolicyFile = policyFile;
         loadPolicyFile();
-
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         if (mStatusBar != null) {
             mStatusBar.setNotificationDelegate(mNotificationDelegate);
@@ -1792,8 +1861,9 @@
         }, mUserProfiles);
 
         final File systemDir = new File(Environment.getDataDirectory(), "system");
+        mRankingThread.start();
 
-        init(Looper.myLooper(),
+        init(Looper.myLooper(), new RankingHandlerWorker(mRankingThread.getLooper()),
                 AppGlobals.getPackageManager(), getContext().getPackageManager(),
                 getLocalService(LightsManager.class),
                 new NotificationListeners(AppGlobals.getPackageManager()),
@@ -2923,21 +2993,52 @@
 
         @Override
         public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException {
+            boolean packagesChanged = false;
             checkCallerIsSystem();
-
             // Cancel posted notifications
+            final int userId = UserHandle.getUserId(uid);
             cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
                     UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
 
-            final String[] packages = new String[] {packageName};
-            final int[] uids = new int[] {uid};
-
-            // Listener & assistant
-            mListeners.onPackagesChanged(true, packages, uids);
-            mAssistants.onPackagesChanged(true, packages, uids);
-
             // Zen
-            mConditionProviders.onPackagesChanged(true, packages, uids);
+            packagesChanged |=
+                    mConditionProviders.resetPackage(packageName, userId);
+
+            // Listener
+            ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
+                    mListeners.resetComponents(packageName, userId);
+            packagesChanged |= changedListeners.get(true).size() > 0
+                    || changedListeners.get(false).size() > 0;
+
+            // When a listener is enabled, we enable the dnd package as a secondary
+            for (int i = 0; i < changedListeners.get(true).size(); i++) {
+                mConditionProviders.setPackageOrComponentEnabled(
+                        changedListeners.get(true).get(i).getPackageName(),
+                        userId, false, true);
+            }
+
+            // Assistant
+            ArrayMap<Boolean, ArrayList<ComponentName>> changedAssistants =
+                    mAssistants.resetComponents(packageName, userId);
+            packagesChanged |= changedAssistants.get(true).size() > 0
+                    || changedAssistants.get(false).size() > 0;
+
+            // we want only one assistant enabled
+            for (int i = 1; i < changedAssistants.get(true).size(); i++) {
+                mAssistants.setPackageOrComponentEnabled(
+                        changedAssistants.get(true).get(i).flattenToString(),
+                        userId, true, false);
+            }
+
+            // When the default assistant is enabled, we enable the dnd package as a secondary
+            if (changedAssistants.get(true).size() > 0) {
+                //we want only one assistant active
+                mConditionProviders
+                        .setPackageOrComponentEnabled(
+                                changedAssistants.get(true).get(0).getPackageName(),
+                                userId, false, true);
+
+            }
 
             // Snoozing
             mSnoozeHelper.clearData(UserHandle.getUserId(uid), packageName);
@@ -2947,6 +3048,14 @@
                 mPreferencesHelper.clearData(packageName, uid);
             }
 
+            if (packagesChanged) {
+                getContext().sendBroadcastAsUser(new Intent(
+                                ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+                                .setPackage(packageName)
+                                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
+                        UserHandle.of(userId), null);
+            }
+
             handleSavePolicyFile();
         }
 
@@ -4958,47 +5067,112 @@
         } else {
             notification.flags &= ~FLAG_BUBBLE;
         }
+        // Is the app in the foreground?
+        final boolean appIsForeground =
+                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (!appIsForeground && metadata != null) {
+            // Remove any flags that only work when foregrounded
+            int flags = metadata.getFlags();
+            flags &= ~Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+            flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+            metadata.setFlags(flags);
+        }
     }
 
     /**
-     * @return whether the provided notification record is allowed to be represented as a bubble.
+     * @return whether the provided notification record is allowed to be represented as a bubble,
+     * accounting for user choice & policy.
      */
     private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
-        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
-        boolean intentCanBubble = metadata != null
-                && canLaunchInActivityView(getContext(), metadata.getIntent(), pkg);
+        if (!canBubble(r, pkg, userId)) {
+            // no log: canBubble has its own
+            return false;
+        }
 
-        // Does the app want to bubble & is able to bubble
-        boolean canBubble = intentCanBubble
-                && mPreferencesHelper.areBubblesAllowed(pkg, userId)
-                && mPreferencesHelper.bubblesEnabled(r.sbn.getUser())
-                && r.getChannel().canBubble()
-                && !mActivityManager.isLowRamDevice();
+        if (mActivityManager.isLowRamDevice()) {
+            logBubbleError(r.getKey(), "low ram device");
+            return false;
+        }
 
-        // Is the app in the foreground?
-        final boolean appIsForeground =
-                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+        if (mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND) {
+            // If the app is foreground it always gets to bubble
+            return true;
+        }
 
-        // Is the notification something we'd allow to bubble?
-        // A call with a foreground service + person
+        if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
+            // This is an update to an active bubble
+            return true;
+        }
+
+        // At this point the bubble must fulfill communication policy
+
+        // Communication always needs a person
         ArrayList<Person> peopleList = notification.extras != null
                 ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
                 : null;
-        boolean isForegroundCall = CATEGORY_CALL.equals(notification.category)
-                && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
-        // OR message style (which always has a person) with any remote input
-        Class<? extends Notification.Style> style = notification.getNotificationStyle();
-        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
-        boolean notificationAppropriateToBubble =
-                (isMessageStyle && hasValidRemoteInput(notification))
-                || (peopleList != null && !peopleList.isEmpty() && isForegroundCall);
+        // Message style requires a person & it's not included in the list
+        boolean isMessageStyle = Notification.MessagingStyle.class.equals(
+                notification.getNotificationStyle());
+        if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
+            logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+                    + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+            return false;
+        }
 
-        // OR something that was previously a bubble & still exists
-        boolean bubbleUpdate = oldRecord != null
-                && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
-        return canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate);
+        // Communication is a message or a call
+        boolean isCall = CATEGORY_CALL.equals(notification.category);
+        boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+        if (isMessageStyle) {
+            if (hasValidRemoteInput(notification)) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "messages require valid remote input");
+            return false;
+        } else if (isCall) {
+            if (hasForegroundService) {
+                return true;
+            }
+            logBubbleError(r.getKey(), "calls require foreground service");
+            return false;
+        }
+        logBubbleError(r.getKey(), "if not foreground, must be "
+                + "Notification.MessageStyle or Notification.CATEGORY_CALL");
+        return false;
+    }
+
+    /**
+     * @return whether the user has enabled the provided notification to bubble, does not account
+     * for policy.
+     */
+    private boolean canBubble(NotificationRecord r, String pkg, int userId) {
+        Notification notification = r.getNotification();
+        Notification.BubbleMetadata metadata = notification.getBubbleMetadata();
+        if (metadata == null) {
+            // no log: no need to inform dev if they didn't attach bubble metadata
+            return false;
+        }
+        if (!canLaunchInActivityView(getContext(), metadata.getIntent(), pkg)) {
+            // no log: method has the failure log
+            return false;
+        }
+        if (!mPreferencesHelper.bubblesEnabled()) {
+            logBubbleError(r.getKey(), "bubbles disabled for user: " + userId);
+            return false;
+        }
+        if (!mPreferencesHelper.areBubblesAllowed(pkg, userId)) {
+            logBubbleError(r.getKey(),
+                    "bubbles for package: " + pkg + " disabled for user: " + userId);
+            return false;
+        }
+        if (!r.getChannel().canBubble()) {
+            logBubbleError(r.getKey(),
+                    "bubbles for channel " + r.getChannel().getId() + " disabled");
+            return false;
+        }
+        return true;
     }
 
     private boolean hasValidRemoteInput(Notification n) {
@@ -5017,6 +5191,11 @@
         return false;
     }
 
+    private void logBubbleError(String key, String failureMessage) {
+        if (DBG) {
+            Log.w(TAG, "Bubble notification: " + key + " failed: " + failureMessage);
+        }
+    }
     /**
      * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
      *
@@ -5365,8 +5544,16 @@
             }
 
             synchronized (mNotificationLock) {
-                // Look for the notification, searching both the posted and enqueued lists.
-                NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
+                // If the notification is currently enqueued, repost this runnable so it has a
+                // chance to notify listeners
+                if ((findNotificationByListLocked(mEnqueuedNotifications, mPkg, mTag, mId, mUserId))
+                        != null) {
+                    mHandler.post(this);
+                    return;
+                }
+                // Look for the notification in the posted list, since we already checked enqueued.
+                NotificationRecord r =
+                        findNotificationByListLocked(mNotificationList, mPkg, mTag, mId, mUserId);
                 if (r != null) {
                     // The notification was found, check if it should be removed.
 
@@ -5383,12 +5570,26 @@
                         return;
                     }
 
+                    // Bubbled children get to stick around if the summary was manually cancelled
+                    // (user removed) from systemui.
+                    FlagChecker childrenFlagChecker = null;
+                    if (mReason == REASON_CANCEL
+                            || mReason == REASON_CLICK
+                            || mReason == REASON_CANCEL_ALL) {
+                        childrenFlagChecker = (flags) -> {
+                            if ((flags & FLAG_BUBBLE) != 0) {
+                                return false;
+                            }
+                            return true;
+                        };
+                    }
+
                     // Cancel the notification.
                     boolean wasPosted = removeFromNotificationListsLocked(r);
                     cancelNotificationLocked(
                             r, mSendDelete, mReason, mRank, mCount, wasPosted, listenerName);
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
-                            mSendDelete, null);
+                            mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
@@ -5558,7 +5759,9 @@
                         notification.flags |=
                                 old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                         r.isUpdate = true;
-                        r.setTextChanged(isVisuallyInterruptive(old, r));
+                        final boolean isInterruptive = isVisuallyInterruptive(old, r);
+                        r.setTextChanged(isInterruptive);
+                        r.setInterruptive(isInterruptive);
                     }
 
                     mNotificationsByKey.put(n.getKey(), r);
@@ -5657,7 +5860,6 @@
 
         Notification oldN = old.sbn.getNotification();
         Notification newN = r.sbn.getNotification();
-
         if (oldN.extras == null || newN.extras == null) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5689,6 +5891,7 @@
             }
             return true;
         }
+
         // Do not compare Spannables (will always return false); compare unstyled Strings
         final String oldText = String.valueOf(oldN.extras.get(Notification.EXTRA_TEXT));
         final String newText = String.valueOf(newN.extras.get(Notification.EXTRA_TEXT));
@@ -5703,6 +5906,7 @@
             }
             return true;
         }
+
         if (oldN.hasCompletedProgress() != newN.hasCompletedProgress()) {
             if (DEBUG_INTERRUPTIVENESS) {
                 Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -5710,6 +5914,16 @@
             }
             return true;
         }
+
+        // Fields below are invisible to bubbles.
+        if (r.canBubble()) {
+            if (DEBUG_INTERRUPTIVENESS) {
+                Slog.v(TAG, "INTERRUPTIVENESS: "
+                        +  r.getKey() + " is not interruptive: bubble");
+            }
+            return false;
+        }
+
         // Actions
         if (Notification.areActionsVisiblyDifferent(oldN, newN)) {
             if (DEBUG_INTERRUPTIVENESS) {
@@ -5743,7 +5957,6 @@
         } catch (Exception e) {
             Slog.w(TAG, "error recovering builder", e);
         }
-
         return false;
     }
 
@@ -5938,12 +6151,17 @@
                     Slog.v(TAG, "INTERRUPTIVENESS: "
                             + record.getKey() + " is not interruptive: summary");
                 }
+            } else if (record.canBubble()) {
+                if (DEBUG_INTERRUPTIVENESS) {
+                    Slog.v(TAG, "INTERRUPTIVENESS: "
+                            + record.getKey() + " is not interruptive: bubble");
+                }
             } else {
+                record.setInterruptive(true);
                 if (DEBUG_INTERRUPTIVENESS) {
                     Slog.v(TAG, "INTERRUPTIVENESS: "
                             + record.getKey() + " is interruptive: alerted");
                 }
-                record.setInterruptive(true);
             }
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
@@ -6302,15 +6520,21 @@
             int indexBefore = findNotificationRecordIndexLocked(record);
             boolean interceptBefore = record.isIntercepted();
             int visibilityBefore = record.getPackageVisibilityOverride();
+            boolean interruptiveBefore = record.isInterruptive();
+
             recon.applyChangesLocked(record);
             applyZenModeLocked(record);
             mRankingHelper.sort(mNotificationList);
-            int indexAfter = findNotificationRecordIndexLocked(record);
-            boolean interceptAfter = record.isIntercepted();
-            int visibilityAfter = record.getPackageVisibilityOverride();
-            changed = indexBefore != indexAfter || interceptBefore != interceptAfter
-                    || visibilityBefore != visibilityAfter;
-            if (interceptBefore && !interceptAfter
+            boolean indexChanged = indexBefore != findNotificationRecordIndexLocked(record);
+            boolean interceptChanged = interceptBefore != record.isIntercepted();
+            boolean visibilityChanged = visibilityBefore != record.getPackageVisibilityOverride();
+
+            // Broadcast isInterruptive changes for bubbles.
+            boolean interruptiveChanged =
+                    record.canBubble() && (interruptiveBefore != record.isInterruptive());
+
+            changed = indexChanged || interceptChanged || visibilityChanged || interruptiveChanged;
+            if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
                 buzzBeepBlinkLocked(record);
             }
@@ -7460,7 +7684,8 @@
                     record.getSound() != null || record.getVibration() != null,
                     record.getSystemGeneratedSmartActions(),
                     record.getSmartReplies(),
-                    record.canBubble()
+                    record.canBubble(),
+                    record.isInterruptive()
             );
             rankings.add(ranking);
         }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d580bd6..082b08deb 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -104,7 +104,7 @@
     @VisibleForTesting
     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
     private static final boolean DEFAULT_SHOW_BADGE = true;
-    private static final boolean DEFAULT_ALLOW_BUBBLE = true;
+    static final boolean DEFAULT_ALLOW_BUBBLE = true;
     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
 
@@ -134,7 +134,7 @@
     private final ZenModeHelper mZenModeHelper;
 
     private SparseBooleanArray mBadgingEnabled;
-    private SparseBooleanArray mBubblesEnabled;
+    private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE;
     private boolean mAreChannelsBypassingDnd;
     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
@@ -1840,40 +1840,19 @@
     }
 
     public void updateBubblesEnabled() {
-        if (mBubblesEnabled == null) {
-            mBubblesEnabled = new SparseBooleanArray();
-        }
-        boolean changed = false;
-        // update the cached values
-        for (int index = 0; index < mBubblesEnabled.size(); index++) {
-            int userId = mBubblesEnabled.keyAt(index);
-            final boolean oldValue = mBubblesEnabled.get(userId);
-            final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.NOTIFICATION_BUBBLES,
-                    DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
-            mBubblesEnabled.put(userId, newValue);
-            changed |= oldValue != newValue;
-        }
-        if (changed) {
+        final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES,
+                DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
+        if (newValue != mBubblesEnabled) {
+            mBubblesEnabled = newValue;
             updateConfig();
         }
     }
 
-    public boolean bubblesEnabled(UserHandle userHandle) {
-        int userId = userHandle.getIdentifier();
-        if (userId == UserHandle.USER_ALL) {
-            return false;
-        }
-        if (mBubblesEnabled.indexOfKey(userId) < 0) {
-            mBubblesEnabled.put(userId,
-                    Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                            Settings.Secure.NOTIFICATION_BUBBLES,
-                            DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
-        }
-        return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
+    public boolean bubblesEnabled() {
+        return mBubblesEnabled;
     }
 
-
     public void updateBadgingEnabled() {
         if (mBadgingEnabled == null) {
             mBadgingEnabled = new SparseBooleanArray();
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 5de00e4..7816f36 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -30,7 +30,7 @@
     boolean canShowBadge(String packageName, int uid);
     boolean badgingEnabled(UserHandle userHandle);
     boolean areBubblesAllowed(String packageName, int uid);
-    boolean bubblesEnabled(UserHandle userHandle);
+    boolean bubblesEnabled();
     boolean isGroupBlocked(String packageName, int uid, String groupId);
 
     Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index c6af756..8f05636 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -179,6 +179,7 @@
             case TYPE_SUPPRESSOR_CHANGED: return "suppressor_changed";
             case TYPE_LISTENER_HINTS_CHANGED: return "listener_hints_changed";
             case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
+            case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
             default: return "unknown";
         }
     }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ee948b2..f63aa52 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -954,12 +954,11 @@
     }
 
     private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) {
-        if (rule.zenMode == NotificationManager.INTERRUPTION_FILTER_NONE) {
+        if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
             policy.apply(new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .build());
-        } else if (rule.zenMode
-                == NotificationManager.INTERRUPTION_FILTER_ALARMS) {
+        } else if (rule.zenMode == Global.ZEN_MODE_ALARMS) {
             policy.apply(new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .allowAlarms(true)
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 965ddc9..f8b3fb2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -744,7 +744,7 @@
                 @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
                 @NonNull final String[] args, @NonNull final ShellCallback callback,
                 @NonNull final ResultReceiver resultReceiver) {
-            (new OverlayManagerShellCommand(this)).exec(
+            (new OverlayManagerShellCommand(getContext(), this)).exec(
                     this, in, out, err, args, callback, resultReceiver);
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 99f5839..eb43275 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -18,15 +18,23 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.RemoteException;
 import android.os.ShellCommand;
 import android.os.UserHandle;
+import android.util.TypedValue;
 
 import java.io.PrintWriter;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Implementation of 'cmd overlay' commands.
@@ -36,9 +44,11 @@
  * for a list of available commands.
  */
 final class OverlayManagerShellCommand extends ShellCommand {
+    private final Context mContext;
     private final IOverlayManager mInterface;
 
-    OverlayManagerShellCommand(@NonNull final IOverlayManager iom) {
+    OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
+        mContext = ctx;
         mInterface = iom;
     }
 
@@ -60,6 +70,8 @@
                     return runEnableExclusive();
                 case "set-priority":
                     return runSetPriority();
+                case "lookup":
+                    return runLookup();
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -102,6 +114,10 @@
         out.println("    'lowest', change priority of PACKAGE to the lowest priority.");
         out.println("    If PARENT is the special keyword 'highest', change priority of");
         out.println("    PACKAGE to the highest priority.");
+        out.println("  lookup [--verbose] PACKAGE-TO-LOAD PACKAGE:TYPE/NAME");
+        out.println("    Load a package and print the value of a given resource");
+        out.println("    applying the current configuration and enabled overlays.");
+        out.println("    For a more fine-grained alernative, use 'idmap2 lookup'.");
     }
 
     private int runList() throws RemoteException {
@@ -253,4 +269,92 @@
             return mInterface.setPriority(packageName, newParentPackageName, userId) ? 0 : 1;
         }
     }
+
+    private int runLookup() throws RemoteException {
+        final PrintWriter out = getOutPrintWriter();
+        final PrintWriter err = getErrPrintWriter();
+
+        final boolean verbose = "--verbose".equals(getNextOption());
+
+        final String packageToLoad = getNextArgRequired();
+
+        final String fullyQualifiedResourceName = getNextArgRequired(); // package:type/name
+        final Pattern regex = Pattern.compile("(.*?):(.*?)/(.*?)");
+        final Matcher matcher = regex.matcher(fullyQualifiedResourceName);
+        if (!matcher.matches()) {
+            err.println("Error: bad resource name, doesn't match package:type/name");
+            return 1;
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            err.println("Error: failed to get package manager");
+            return 1;
+        }
+
+        final Resources res;
+        try {
+            res = pm.getResourcesForApplication(packageToLoad);
+        } catch (PackageManager.NameNotFoundException e) {
+            err.println("Error: failed to get resources for package " + packageToLoad);
+            return 1;
+        }
+        final AssetManager assets = res.getAssets();
+        try {
+            assets.setResourceResolutionLoggingEnabled(true);
+
+            // first try as non-complex type ...
+            try {
+                final TypedValue value = new TypedValue();
+                res.getValue(fullyQualifiedResourceName, value, false /* resolveRefs */);
+                final CharSequence valueString = value.coerceToString();
+                final String resolution = assets.getLastResourceResolution();
+
+                res.getValue(fullyQualifiedResourceName, value, true /* resolveRefs */);
+                final CharSequence resolvedString = value.coerceToString();
+
+                if (verbose) {
+                    out.println(resolution);
+                }
+
+                if (valueString.equals(resolvedString)) {
+                    out.println(valueString);
+                } else {
+                    out.println(valueString + " -> " + resolvedString);
+                }
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // this is ok, resource could still be a complex type
+            }
+
+            // ... then try as complex type
+            try {
+
+                final String pkg = matcher.group(1);
+                final String type = matcher.group(2);
+                final String name = matcher.group(3);
+                final int resid = res.getIdentifier(name, type, pkg);
+                if (resid == 0) {
+                    throw new Resources.NotFoundException();
+                }
+                final TypedArray array = res.obtainTypedArray(resid);
+                if (verbose) {
+                    out.println(assets.getLastResourceResolution());
+                }
+                TypedValue tv = new TypedValue();
+                for (int i = 0; i < array.length(); i++) {
+                    array.getValue(i, tv);
+                    out.println(tv.coerceToString());
+                }
+                array.recycle();
+                return 0;
+            } catch (Resources.NotFoundException e) {
+                // give up
+                err.println("Error: failed to get the resource " + fullyQualifiedResourceName);
+                return 1;
+            }
+        } finally {
+            assets.setResourceResolutionLoggingEnabled(false);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 7eb7438..61ea84f 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -16,13 +16,18 @@
 
 package com.android.server.pm;
 
+import static android.content.pm.PackageParser.Component;
+import static android.content.pm.PackageParser.IntentInfo;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
 import android.Manifest;
 import android.annotation.Nullable;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
@@ -34,10 +39,12 @@
 import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.FgThread;
 import com.android.server.compat.PlatformCompat;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -49,19 +56,16 @@
  * The entity responsible for filtering visibility between apps based on declarations in their
  * manifests.
  */
-class AppsFilter {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class AppsFilter {
 
     private static final String TAG = PackageManagerService.TAG;
 
-    // Forces filtering logic to run for debug purposes.
-    // STOPSHIP (b/136675067): should be false after development is complete
-    private static final boolean DEBUG_RUN_WHEN_DISABLED = true;
-
     // Logs all filtering instead of enforcing
     private static final boolean DEBUG_ALLOW_ALL = false;
 
     @SuppressWarnings("ConstantExpression")
-    private static final boolean DEBUG_LOGGING = false | DEBUG_RUN_WHEN_DISABLED | DEBUG_ALLOW_ALL;
+    private static final boolean DEBUG_LOGGING = false | DEBUG_ALLOW_ALL;
 
     /**
      * This contains a list of packages that are implicitly queryable because another app explicitly
@@ -122,15 +126,13 @@
 
         /** @return true if the feature is enabled for the given package. */
         boolean packageIsEnabled(PackageParser.Package pkg);
+
     }
 
     private static class FeatureConfigImpl implements FeatureConfig {
         private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
-
-        // STOPSHIP(patb): set this to true if we plan to launch this in R
-        private static final boolean DEFAULT_ENABLED_STATE = false;
         private final PackageManagerService.Injector mInjector;
-        private volatile boolean mFeatureEnabled = DEFAULT_ENABLED_STATE;
+        private volatile boolean mFeatureEnabled = true;
 
         private FeatureConfigImpl(PackageManagerService.Injector injector) {
             mInjector = injector;
@@ -140,13 +142,13 @@
         public void onSystemReady() {
             mFeatureEnabled = DeviceConfig.getBoolean(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
-                    DEFAULT_ENABLED_STATE);
+                    true);
             DeviceConfig.addOnPropertiesChangedListener(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
                     properties -> {
                         synchronized (FeatureConfigImpl.this) {
                             mFeatureEnabled = properties.getBoolean(
-                                    FILTERING_ENABLED_NAME, DEFAULT_ENABLED_STATE);
+                                    FILTERING_ENABLED_NAME, true);
                         }
                     });
         }
@@ -200,12 +202,38 @@
             return false;
         }
         for (Intent intent : querying.mQueriesIntents) {
-            for (PackageParser.Activity activity : potentialTarget.activities) {
-                if (activity.intents != null) {
-                    for (PackageParser.ActivityIntentInfo filter : activity.intents) {
-                        if (matches(intent, filter)) {
-                            return true;
-                        }
+            if (matches(intent, potentialTarget.providers, potentialTarget.activities,
+                    potentialTarget.services, potentialTarget.receivers)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean matches(Intent intent,
+            ArrayList<PackageParser.Provider> providerList,
+            ArrayList<? extends Component<? extends IntentInfo>>... componentLists) {
+        for (int p = providerList.size() - 1; p >= 0; p--) {
+            PackageParser.Provider provider = providerList.get(p);
+            final ProviderInfo providerInfo = provider.info;
+            final Uri data = intent.getData();
+            if ("content".equalsIgnoreCase(intent.getScheme())
+                    && data != null
+                    && providerInfo.authority.equalsIgnoreCase(data.getAuthority())) {
+                return true;
+            }
+        }
+
+        for (int l = componentLists.length - 1; l >= 0; l--) {
+            ArrayList<? extends Component<? extends IntentInfo>> components = componentLists[l];
+            for (int c = components.size() - 1; c >= 0; c--) {
+                Component<? extends IntentInfo> component = components.get(c);
+                ArrayList<? extends IntentInfo> intents = component.intents;
+                for (int i = intents.size() - 1; i >= 0; i--) {
+                    IntentFilter intentFilter = intents.get(i);
+                    if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
+                            intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
+                        return true;
                     }
                 }
             }
@@ -213,32 +241,26 @@
         return false;
     }
 
-    /** Returns true if the given intent matches the given filter. */
-    private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) {
-        return filter.match(intent.getAction(), intent.getType(), intent.getScheme(),
-                intent.getData(), intent.getCategories(), "AppsFilter") > 0;
-    }
-
     /**
-     * Marks that a package initiated an interaction with another package, granting visibility of
-     * the prior from the former.
+     * Grants access based on an interaction between a calling and target package, granting
+     * visibility of the caller from the target.
      *
-     * @param initiatingPackage the package initiating the interaction
+     * @param callingPackage    the package initiating the interaction
      * @param targetPackage     the package being interacted with and thus gaining visibility of the
      *                          initiating package.
      * @param userId            the user in which this interaction was taking place
      */
-    private void markAppInteraction(
-            PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) {
+    public void grantImplicitAccess(
+            String callingPackage, String targetPackage, int userId) {
         HashMap<String, Set<String>> currentUser = mImplicitlyQueryable.get(userId);
         if (currentUser == null) {
             currentUser = new HashMap<>();
             mImplicitlyQueryable.put(userId, currentUser);
         }
-        if (!currentUser.containsKey(targetPackage.pkg.packageName)) {
-            currentUser.put(targetPackage.pkg.packageName, new HashSet<>());
+        if (!currentUser.containsKey(targetPackage)) {
+            currentUser.put(targetPackage, new HashSet<>());
         }
-        currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName);
+        currentUser.get(targetPackage).add(callingPackage);
     }
 
     public void onSystemReady() {
@@ -327,10 +349,16 @@
     public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
             PackageSetting targetPkgSetting, int userId) {
         final boolean featureEnabled = mFeatureConfig.isGloballyEnabled();
-        if (!featureEnabled && !DEBUG_RUN_WHEN_DISABLED) {
+        if (!featureEnabled) {
+            if (DEBUG_LOGGING) {
+                Slog.d(TAG, "filtering disabled; skipped");
+            }
             return false;
         }
         if (callingUid < Process.FIRST_APPLICATION_UID) {
+            if (DEBUG_LOGGING) {
+                Slog.d(TAG, "filtering skipped; " + callingUid + " is system");
+            }
             return false;
         }
         if (callingSetting == null) {
@@ -342,8 +370,6 @@
             callingPkgSetting = (PackageSetting) callingSetting;
             if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting,
                     userId)) {
-                // TODO: actually base this on a start / launch (not just a query)
-                markAppInteraction(callingPkgSetting, targetPkgSetting, userId);
                 return false;
             }
         } else if (callingSetting instanceof SharedUserSetting) {
@@ -354,8 +380,6 @@
                     final PackageSetting packageSetting = packageSettings.valueAt(i);
                     if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting,
                             userId)) {
-                        // TODO: actually base this on a start / launch (not just a query)
-                        markAppInteraction(packageSetting, targetPkgSetting, userId);
                         return false;
                     }
                     if (callingPkgSetting == null && packageSetting.pkg != null) {
@@ -371,22 +395,12 @@
                 return true;
             }
         }
-        if (!featureEnabled) {
-            return false;
+
+        if (DEBUG_LOGGING) {
+            log(callingPkgSetting, targetPkgSetting,
+                    DEBUG_ALLOW_ALL ? "ALLOWED" : "BLOCKED");
         }
-        if (mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
-            if (DEBUG_LOGGING) {
-                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
-                        + targetPkgSetting.name + (DEBUG_ALLOW_ALL ? " ALLOWED" : "BLOCKED"));
-            }
-            return !DEBUG_ALLOW_ALL;
-        } else {
-            if (DEBUG_LOGGING) {
-                Slog.d(TAG, "interaction: " + callingPkgSetting.name + " -> "
-                        + targetPkgSetting.name + " DISABLED");
-            }
-            return false;
-        }
+        return !DEBUG_ALLOW_ALL;
     }
 
     private boolean shouldFilterApplicationInternal(
@@ -394,41 +408,74 @@
         final String callingName = callingPkgSetting.pkg.packageName;
         final PackageParser.Package targetPkg = targetPkgSetting.pkg;
 
+        if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "DISABLED");
+            }
+            return false;
+        }
         // This package isn't technically installed and won't be written to settings, so we can
         // treat it as filtered until it's available again.
         if (targetPkg == null) {
+            if (DEBUG_LOGGING) {
+                Slog.wtf(TAG, "shouldFilterApplication: " + "targetPkg is null");
+            }
             return true;
         }
         final String targetName = targetPkg.packageName;
         if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "caller pre-R");
+            }
             return false;
         }
         if (isImplicitlyQueryableSystemApp(targetPkgSetting)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "implicitly queryable sys");
+            }
             return false;
         }
         if (targetPkg.mForceQueryable) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "manifest forceQueryable");
+            }
             return false;
         }
         if (mForceQueryable.contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "whitelist forceQueryable");
+            }
             return false;
         }
         if (mQueriesViaPackage.containsKey(callingName)
                 && mQueriesViaPackage.get(callingName).contains(
                 targetName)) {
             // the calling package has explicitly declared the target package; allow
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "queries package");
+            }
             return false;
         } else if (mQueriesViaIntent.containsKey(callingName)
                 && mQueriesViaIntent.get(callingName).contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "queries intent");
+            }
             return false;
         }
         if (mImplicitlyQueryable.get(userId) != null
                 && mImplicitlyQueryable.get(userId).containsKey(callingName)
                 && mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) {
+            if (DEBUG_LOGGING) {
+                log(callingPkgSetting, targetPkgSetting, "implicitly queryable for user");
+            }
             return false;
         }
         if (callingPkgSetting.pkg.instrumentation.size() > 0) {
             for (int i = 0, max = callingPkgSetting.pkg.instrumentation.size(); i < max; i++) {
                 if (callingPkgSetting.pkg.instrumentation.get(i).info.targetPackage == targetName) {
+                    if (DEBUG_LOGGING) {
+                        log(callingPkgSetting, targetPkgSetting, "instrumentation");
+                    }
                     return false;
                 }
             }
@@ -437,6 +484,9 @@
             if (mPermissionManager.checkPermission(
                     Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId)
                     == PackageManager.PERMISSION_GRANTED) {
+                if (DEBUG_LOGGING) {
+                    log(callingPkgSetting, targetPkgSetting, "permission");
+                }
                 return false;
             }
         } catch (RemoteException e) {
@@ -445,6 +495,13 @@
         return true;
     }
 
+    private static void log(PackageSetting callingPkgSetting, PackageSetting targetPkgSetting,
+            String description) {
+        Slog.wtf(TAG,
+                "interaction: " + callingPkgSetting.name + " -> " + targetPkgSetting.name + " "
+                        + description);
+    }
+
     private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) {
         return targetPkgSetting.isSystem() && (mSystemAppsQueryable
                 || mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName));
@@ -467,7 +524,9 @@
         for (int user : users) {
             pw.append("    User ").append(Integer.toString(user)).println(":");
             final HashMap<String, Set<String>> queryMapForUser = mImplicitlyQueryable.get(user);
-            dumpQueriesMap(pw, filteringPackageName, queryMapForUser, "      ");
+            if (queryMapForUser != null) {
+                dumpQueriesMap(pw, filteringPackageName, queryMapForUser, "      ");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/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 41403fd..d03caef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -113,6 +113,7 @@
 import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
 
 import android.Manifest;
+import android.annotation.AppIdInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -378,14 +379,14 @@
  * <p>
  * Internally there are two important locks:
  * <ul>
- * <li>{@link #mPackages} is used to guard all in-memory parsed package details
+ * <li>{@link #mLock} is used to guard all in-memory parsed package details
  * and other related state. It is a fine-grained lock that should only be held
  * momentarily, as it's one of the most contended locks in the system.
  * <li>{@link #mInstallLock} is used to guard all {@code installd} access, whose
  * operations typically involve heavy lifting of application data on disk. Since
  * {@code installd} is single-threaded, and it's operations can often be slow,
- * this lock should never be acquired while already holding {@link #mPackages}.
- * Conversely, it's safe to acquire {@link #mPackages} momentarily while already
+ * this lock should never be acquired while already holding {@link #mLock}.
+ * Conversely, it's safe to acquire {@link #mLock} momentarily while already
  * holding {@link #mInstallLock}.
  * </ul>
  * Many internal methods rely on the caller to hold the appropriate locks, and
@@ -394,8 +395,8 @@
  * <li>fooLI(): the caller must hold {@link #mInstallLock}
  * <li>fooLIF(): the caller must hold {@link #mInstallLock} and the package
  * being modified must be frozen
- * <li>fooLPr(): the caller must hold {@link #mPackages} for reading
- * <li>fooLPw(): the caller must hold {@link #mPackages} for writing
+ * <li>fooLPr(): the caller must hold {@link #mLock} for reading
+ * <li>fooLPw(): the caller must hold {@link #mLock} for writing
  * </ul>
  * <p>
  * Because this class is very central to the platform's security; please run all
@@ -580,16 +581,6 @@
 
     private static final String PACKAGE_SCHEME = "package";
 
-    private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
-
-    private static final String PRODUCT_OVERLAY_DIR = "/product/overlay";
-
-    private static final String SYSTEM_EXT_OVERLAY_DIR = "/system_ext/overlay";
-
-    private static final String ODM_OVERLAY_DIR = "/odm/overlay";
-
-    private static final String OEM_OVERLAY_DIR = "/oem/overlay";
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -669,7 +660,7 @@
 
     // Lock for global state used when modifying package state or settings.
     // Methods that must be called with this lock held have
-    // the suffix "Locked". Some methods may use the legacy the suffix "LP"
+    // the suffix "Locked". Some methods may use the legacy suffix "LP"
     final Object mLock;
 
     // Keys are String (package name), values are Package.
@@ -755,6 +746,26 @@
     private final Injector mInjector;
 
     /**
+     * The list of all system partitions that may contain packages in ascending order of
+     * specificity (the more generic, the earlier in the list a partition appears).
+     */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
+            Arrays.asList(
+                    new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
+                            true /* hasPriv */, false /* hasOverlays */),
+                    new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
+                            false /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
+                            true /* hasPriv */, true /* hasOverlays */),
+                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
+                            true /* hasPriv */, true /* hasOverlays */)));
+
+    /**
      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
      *
      * NOTE: All getters should return the same instance for every call.
@@ -2551,6 +2562,51 @@
         }
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static class SystemPartition {
+        public final File folder;
+        public final int scanFlag;
+        public final File appFolder;
+        @Nullable
+        public final File privAppFolder;
+        @Nullable
+        public final File overlayFolder;
+
+        private SystemPartition(File folder, int scanFlag, boolean hasPrivApps,
+                boolean hasOverlays) {
+            this.folder = folder;
+            this.scanFlag = scanFlag;
+            this.appFolder = toCanonical(new File(folder, "app"));
+            this.privAppFolder = hasPrivApps ? toCanonical(new File(folder, "priv-app")) : null;
+            this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
+        }
+
+        public boolean containsPrivApp(File scanFile) {
+            return FileUtils.contains(privAppFolder, scanFile);
+        }
+
+        public boolean containsApp(File scanFile) {
+            return FileUtils.contains(appFolder, scanFile);
+        }
+
+        public boolean containsPath(String path) {
+            return path.startsWith(folder.getPath() + "/");
+        }
+
+        public boolean containsPrivPath(String path) {
+            return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
+        }
+
+        private static File toCanonical(File dir) {
+            try {
+                return dir.getCanonicalFile();
+            } catch (IOException e) {
+                // failed to look up canonical path, continue with original one
+                return dir;
+            }
+        }
+    }
+
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
@@ -2774,215 +2830,35 @@
             // any apps.)
             // For security and version matching reason, only consider overlay packages if they
             // reside in the right directory.
-            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-            scanDirTracedLI(new File(PRODUCT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-            scanDirTracedLI(new File(SYSTEM_EXT_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
-            scanDirTracedLI(new File(ODM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_ODM,
-                    0);
-            scanDirTracedLI(new File(OEM_OVERLAY_DIR),
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
+            final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
+            final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+            for (int i = SYSTEM_PARTITIONS.size() - 1; i >= 0; i--) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.overlayFolder == null) {
+                    continue;
+                }
+                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
+            }
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
 
-            // Find base frameworks (resource packages without code).
-            scanDirTracedLI(frameworkDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_NO_DEX
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
+            scanDirTracedLI(frameworkDir, systemParseFlags,
+                    systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
             if (!mPackages.containsKey("android")) {
                 throw new IllegalStateException(
                         "Failed to load frameworks package; check log for warnings");
             }
-
-            // Collect privileged system packages.
-            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            scanDirTracedLI(privilegedAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary system packages.
-            final File systemAppDir = new File(Environment.getRootDirectory(), "app");
-            scanDirTracedLI(systemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM,
-                    0);
-
-            // Collect privileged vendor packages.
-            File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
-            try {
-                privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
+            for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
+                final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
+                if (partition.privAppFolder != null) {
+                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
+                            systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
+                }
+                scanDirTracedLI(partition.appFolder, systemParseFlags,
+                        systemScanFlags | partition.scanFlag, 0);
             }
-            scanDirTracedLI(privilegedVendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
 
-            // Collect ordinary vendor packages.
-            File vendorAppDir = new File(Environment.getVendorDirectory(), "app");
-            try {
-                vendorAppDir = vendorAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(vendorAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect privileged odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File privilegedOdmAppDir = new File(Environment.getOdmDirectory(),
-                        "priv-app");
-            try {
-                privilegedOdmAppDir = privilegedOdmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedOdmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary odm packages. /odm is another vendor partition
-            // other than /vendor.
-            File odmAppDir = new File(Environment.getOdmDirectory(), "app");
-            try {
-                odmAppDir = odmAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(odmAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_VENDOR,
-                    0);
-
-            // Collect all OEM packages.
-            final File oemAppDir = new File(Environment.getOemDirectory(), "app");
-            scanDirTracedLI(oemAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_OEM,
-                    0);
-
-            // Collected privileged /product packages.
-            File privilegedProductAppDir = new File(Environment.getProductDirectory(), "priv-app");
-            try {
-                privilegedProductAppDir = privilegedProductAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedProductAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /product packages.
-            File productAppDir = new File(Environment.getProductDirectory(), "app");
-            try {
-                productAppDir = productAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(productAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_PRODUCT,
-                    0);
-
-            // Collected privileged /system_ext packages.
-            File privilegedSystemExtAppDir =
-                    new File(Environment.getSystemExtDirectory(), "priv-app");
-            try {
-                privilegedSystemExtAppDir =
-                        privilegedSystemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(privilegedSystemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT
-                    | SCAN_AS_PRIVILEGED,
-                    0);
-
-            // Collect ordinary /system_ext packages.
-            File systemExtAppDir = new File(Environment.getSystemExtDirectory(), "app");
-            try {
-                systemExtAppDir = systemExtAppDir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-            }
-            scanDirTracedLI(systemExtAppDir,
-                    mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM_DIR,
-                    scanFlags
-                    | SCAN_AS_SYSTEM
-                    | SCAN_AS_SYSTEM_EXT,
-                    0);
 
             // Prune any system packages that no longer exist.
             final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -3150,89 +3026,26 @@
                         logCriticalInfo(Log.WARN, "Expected better " + packageName
                                 + " but never showed up; reverting to system");
 
-                        final @ParseFlags int reparseFlags;
-                        final @ScanFlags int rescanFlags;
-                        if (FileUtils.contains(privilegedAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM;
-                        } else if (FileUtils.contains(privilegedVendorAppDir, scanFile)
-                                || FileUtils.contains(privilegedOdmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(vendorAppDir, scanFile)
-                                || FileUtils.contains(odmAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_VENDOR;
-                        } else if (FileUtils.contains(oemAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_OEM;
-                        } else if (FileUtils.contains(privilegedProductAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(productAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_PRODUCT;
-                        } else if (FileUtils.contains(privilegedSystemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT
-                                    | SCAN_AS_PRIVILEGED;
-                        } else if (FileUtils.contains(systemExtAppDir, scanFile)) {
-                            reparseFlags =
-                                    mDefParseFlags |
-                                    PackageParser.PARSE_IS_SYSTEM_DIR;
-                            rescanFlags =
-                                    scanFlags
-                                    | SCAN_AS_SYSTEM
-                                    | SCAN_AS_SYSTEM_EXT;
-                        } else {
+                        @ParseFlags int reparseFlags = 0;
+                        @ScanFlags int rescanFlags = 0;
+                        for (int i1 = 0, size = SYSTEM_PARTITIONS.size(); i1 < size; i1++) {
+                            SystemPartition partition = SYSTEM_PARTITIONS.get(i1);
+                            if (partition.containsPrivApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
+                                        | partition.scanFlag;
+                                break;
+                            }
+                            if (partition.containsApp(scanFile)) {
+                                reparseFlags = systemParseFlags;
+                                rescanFlags = systemScanFlags | partition.scanFlag;
+                                break;
+                            }
+                        }
+                        if (rescanFlags == 0) {
                             Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                             continue;
                         }
-
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
@@ -6531,13 +6344,13 @@
     }
 
     // TODO: handle preferred activities missing while user has amnesia
-    /** <b>must not hold {@link #mPackages}</b> */
+    /** <b>must not hold {@link #mLock}</b> */
     ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
             List<ResolveInfo> query, int priority, boolean always,
             boolean removeMatches, boolean debug, int userId) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
@@ -12059,11 +11872,12 @@
                 | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
     }
 
-    private void killApplication(String pkgName, int appId, String reason) {
+    private void killApplication(String pkgName, @AppIdInt int appId, String reason) {
         killApplication(pkgName, appId, UserHandle.USER_ALL, reason);
     }
 
-    private void killApplication(String pkgName, int appId, int userId, String reason) {
+    private void killApplication(String pkgName, @AppIdInt int appId,
+            @UserIdInt int userId, String reason) {
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -12438,7 +12252,7 @@
 
     @Override
     public void sendPackageAddedForNewUsers(String packageName, boolean sendBootCompleted,
-            boolean includeStopped, int appId, int[] userIds, int[] instantUserIds) {
+            boolean includeStopped, @AppIdInt int appId, int[] userIds, int[] instantUserIds) {
         if (ArrayUtils.isEmpty(userIds) && ArrayUtils.isEmpty(instantUserIds)) {
             return;
         }
@@ -13788,8 +13602,7 @@
     private void restoreAndPostInstall(
             int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
         if (DEBUG_INSTALL) {
-            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package="
-                    + res.pkg.packageName);
+            Log.v(TAG, "restoreAndPostInstall userId=" + userId + " package=" + res.pkg);
         }
 
         // A restore should be performed at this point if (a) the install
@@ -13853,7 +13666,7 @@
 
         // If this is an update to a package that might be potentially downgraded, then we
         // need to check with the rollback manager whether there's any userdata that might
-        // need to be restored for the package.
+        // need to be snapshotted or restored for the package.
         //
         // TODO(narayan): Get this working for cases where userId == UserHandle.USER_ALL.
         if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
@@ -13880,12 +13693,16 @@
                 installedUsers = ps.queryInstalledUsers(allUsers, true);
             }
 
-            if (ps != null) {
+            boolean doSnapshotOrRestore = data != null && data.args != null
+                    && ((data.args.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                    || (data.args.installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
+
+            if (ps != null && doSnapshotOrRestore) {
                 try {
                     rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
                             seInfo, token);
                 } catch (RemoteException re) {
-                    // Cannot happen, the RollbackManager is hosted in the same process.
+                    Log.e(TAG, "Error snapshotting/restoring user data: " + re);
                 }
                 doRestore = true;
             }
@@ -16196,7 +16013,7 @@
     /**
      * On successful install, executes remaining steps after commit completes and the package lock
      * is released. These are typically more expensive or require calls to installd, which often
-     * locks on {@link #mPackages}.
+     * locks on {@link #mLock}.
      */
     private void executePostCommitSteps(CommitRequest commitRequest) {
         for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
@@ -17119,8 +16936,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);
                     }
@@ -17926,7 +17742,7 @@
             }
             if (removedAppId >= 0) {
                 packageSender.sendPackageBroadcast(Intent.ACTION_UID_REMOVED,
-                    null, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
+                        removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND,
                     null, null, broadcastUsers, instantUserIds);
             }
         }
@@ -18014,19 +17830,20 @@
                         // or packages running under the shared user of the removed
                         // package if revoking the permissions requested only by the removed
                         // package is successful and this causes a change in gids.
+                        boolean shouldKill = false;
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
                                     userId);
-                            if (userIdToKill == UserHandle.USER_ALL
-                                    || userIdToKill >= UserHandle.USER_SYSTEM) {
-                                // If gids changed for this user, kill all affected packages.
-                                mHandler.post(() -> {
-                                    // This has to happen with no lock held.
-                                    killApplication(deletedPs.name, deletedPs.appId,
-                                            KILL_APP_REASON_GIDS_CHANGED);
-                                });
-                                break;
-                            }
+                            shouldKill |= userIdToKill == UserHandle.USER_ALL
+                                    || userIdToKill >= UserHandle.USER_SYSTEM;
+                        }
+                        // If gids changed, kill all affected packages.
+                        if (shouldKill) {
+                            mHandler.post(() -> {
+                                // This has to happen with no lock held.
+                                killApplication(deletedPs.name, deletedPs.appId,
+                                        KILL_APP_REASON_GIDS_CHANGED);
+                            });
                         }
                     }
                     clearPackagePreferredActivitiesLPw(
@@ -18074,70 +17891,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.
@@ -18248,23 +18010,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);
@@ -18899,7 +18653,8 @@
      * Remove entries from the keystore daemon. Will only remove it if the
      * {@code appId} is valid.
      */
-    private static void removeKeystoreDataIfNeeded(UserManagerInternal um, int userId, int appId) {
+    private static void removeKeystoreDataIfNeeded(UserManagerInternal um, @UserIdInt int userId,
+            @AppIdInt int appId) {
         if (appId < 0) {
             return;
         }
@@ -18985,7 +18740,7 @@
     }
 
     @Override
-    public void getPackageSizeInfo(final String packageName, int userHandle,
+    public void getPackageSizeInfo(final String packageName, int userId,
             final IPackageStatsObserver observer) {
         throw new UnsupportedOperationException(
                 "Shame on you for calling the hidden API getPackageSizeInfo(). Shame!");
@@ -19780,7 +19535,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);
@@ -19873,11 +19628,11 @@
         return null;
     }
 
-    /** <b>must not hold {@link #mPackages}</b> */
+    /** <b>must not hold {@link #mLock}</b> */
     private void updateDefaultHomeNotLocked(SparseBooleanArray userIds) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         for (int i = userIds.size() - 1; i >= 0; --i) {
             final int userId = userIds.keyAt(i);
@@ -19886,14 +19641,14 @@
     }
 
     /**
-     * <b>must not hold {@link #mPackages}</b>
+     * <b>must not hold {@link #mLock}</b>
      *
      * @return Whether the ACTION_PREFERRED_ACTIVITY_CHANGED broadcast has been scheduled.
      */
     private boolean updateDefaultHomeNotLocked(int userId) {
         if (Thread.holdsLock(mLock)) {
             Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
-                    + " is holding mPackages", new Throwable());
+                    + " is holding mLock", new Throwable());
         }
         if (!mSystemReady) {
             // We might get called before system is ready because of package changes etc, but
@@ -21824,7 +21579,7 @@
      * will try recovering system apps by wiping data; third-party app data is
      * left intact.
      * <p>
-     * <em>Note: To avoid a deadlock, do not call this method with {@code mPackages} lock held</em>
+     * <em>Note: To avoid a deadlock, do not call this method with {@code mLock} lock held</em>
      */
     private void prepareAppDataAfterInstallLIF(PackageParser.Package pkg) {
         final PackageSetting ps;
@@ -22490,24 +22245,24 @@
     }
 
     /** Called by UserManagerService */
-    void cleanUpUser(UserManagerService userManager, int userHandle) {
+    void cleanUpUser(UserManagerService userManager, @UserIdInt int userId) {
         synchronized (mLock) {
-            mDirtyUsers.remove(userHandle);
-            mUserNeedsBadging.delete(userHandle);
-            mSettings.removeUserLPw(userHandle);
-            mPendingBroadcasts.remove(userHandle);
-            mInstantAppRegistry.onUserRemovedLPw(userHandle);
-            removeUnusedPackagesLPw(userManager, userHandle);
+            mDirtyUsers.remove(userId);
+            mUserNeedsBadging.delete(userId);
+            mSettings.removeUserLPw(userId);
+            mPendingBroadcasts.remove(userId);
+            mInstantAppRegistry.onUserRemovedLPw(userId);
+            removeUnusedPackagesLPw(userManager, userId);
         }
     }
 
     /**
-     * We're removing userHandle and would like to remove any downloaded packages
+     * We're removing userId and would like to remove any downloaded packages
      * that are no longer in use by any other user.
-     * @param userHandle the user being removed
+     * @param userId the user being removed
      */
     @GuardedBy("mLock")
-    private void removeUnusedPackagesLPw(UserManagerService userManager, final int userHandle) {
+    private void removeUnusedPackagesLPw(UserManagerService userManager, final int userId) {
         final boolean DEBUG_CLEAN_APKS = false;
         int [] users = userManager.getUserIds();
         Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
@@ -22531,7 +22286,7 @@
                 }
             } else {
                 for (int i = 0; i < users.length; i++) {
-                    if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                    if (users[i] != userId && ps.getInstalled(users[i])) {
                         keep = true;
                         if (DEBUG_CLEAN_APKS) {
                             Slog.i(TAG, "  Keeping package " + packageName + " for user "
@@ -22547,7 +22302,7 @@
                 }
                 //end run
                 mHandler.post(() -> deletePackageX(packageName, PackageManager.VERSION_CODE_HIGHEST,
-                        userHandle, 0));
+                        userId, 0));
             }
         }
     }
@@ -23373,11 +23128,24 @@
         }
 
         @Override
-        public void grantEphemeralAccess(int userId, Intent intent,
-                int targetAppId, int ephemeralAppId) {
+        public void grantImplicitAccess(int userId, Intent intent,
+                int callingAppId, int targetAppId) {
             synchronized (mLock) {
-                mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
-                        targetAppId, ephemeralAppId);
+                final PackageParser.Package callingPackage = getPackage(
+                        UserHandle.getUid(userId, callingAppId));
+                final PackageParser.Package targetPackage = getPackage(
+                        UserHandle.getUid(userId, targetAppId));
+                if (callingPackage == null || targetPackage == null) {
+                    return;
+                }
+
+                if (isInstantApp(callingPackage.packageName, userId)) {
+                    mInstantAppRegistry.grantInstantAccessLPw(userId, intent,
+                            callingAppId, 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 af1a095..fe529a1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -276,7 +276,7 @@
                 case "get-harmful-app-warning":
                     return runGetHarmfulAppWarning();
                 case "get-stagedsessions":
-                    return getStagedSessions();
+                    return runListStagedSessions();
                 case "uninstall-system-updates":
                     return uninstallSystemUpdates();
                 case "rollback-app":
@@ -348,53 +348,6 @@
         return 1;
     }
 
-    private int getStagedSessions() {
-        final IndentingPrintWriter pw = new IndentingPrintWriter(
-                getOutPrintWriter(), /* singleIndent */ "  ", /* wrapLength */ 120);
-        try {
-            List<SessionInfo> stagedSessions =
-                    mInterface.getPackageInstaller().getStagedSessions().getList();
-            final SparseArray<SessionInfo> sessionById = new SparseArray<>(stagedSessions.size());
-            for (SessionInfo session : stagedSessions) {
-                sessionById.put(session.getSessionId(), session);
-            }
-            for (SessionInfo session: stagedSessions) {
-                if (session.getParentSessionId() != SessionInfo.INVALID_ID) {
-                    continue;
-                }
-                printStagedSession(session, pw);
-                if (session.isMultiPackage()) {
-                    pw.increaseIndent();
-                    final int[] childIds = session.getChildSessionIds();
-                    for (int i = 0; i < childIds.length; i++) {
-                        final SessionInfo childSession = sessionById.get(childIds[i]);
-                        if (childSession == null) {
-                            pw.println("sessionId = " + childIds[i] + "; not found");
-                        } else {
-                            printStagedSession(childSession, pw);
-                        }
-                    }
-                    pw.decreaseIndent();
-                }
-            }
-        } catch (RemoteException e) {
-            pw.println("Failure ["
-                    + e.getClass().getName() + " - "
-                    + e.getMessage() + "]");
-            return 0;
-        }
-        return 1;
-    }
-
-    private static void printStagedSession(SessionInfo session, PrintWriter pw) {
-        pw.println("sessionId = " + session.getSessionId()
-                + "; appPackageName = " + session.getAppPackageName()
-                + "; isStaged = " + session.isStaged()
-                + "; isReady = " + session.isStagedSessionReady()
-                + "; isApplied = " + session.isStagedSessionApplied()
-                + "; isFailed = " + session.isStagedSessionFailed() + ";");
-    }
-
     private int uninstallSystemUpdates() {
         final PrintWriter pw = getOutPrintWriter();
         List<String> failedUninstalls = new LinkedList<>();
@@ -567,6 +520,8 @@
                 return runListPermissionGroups();
             case "permissions":
                 return runListPermissions();
+            case "staged-sessions":
+                return runListStagedSessions();
             case "users":
                 ServiceManager.getService("user").shellCommand(
                         getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
@@ -904,6 +859,103 @@
         return 0;
     }
 
+    private static class SessionDump {
+        boolean onlyParent; // Show parent sessions only
+        boolean onlyReady; // Show only staged sessions that are in ready state
+        boolean onlySessionId; // Show sessionId only
+    }
+
+    // Returns true if the provided flag is a session flag and given SessionDump was updated
+    private boolean setSessionFlag(String flag, SessionDump sessionDump) {
+        switch (flag) {
+            case "--only-parent":
+                sessionDump.onlyParent = true;
+                break;
+            case "--only-ready":
+                sessionDump.onlyReady = true;
+                break;
+            case "--only-sessionid":
+                sessionDump.onlySessionId = true;
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private int runListStagedSessions() {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(
+                getOutPrintWriter(), /* singleIndent */ "  ", /* wrapLength */ 120);
+
+        SessionDump sessionDump = new SessionDump();
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (!setSessionFlag(opt, sessionDump)) {
+                pw.println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+
+        try {
+            List<SessionInfo> stagedSessions =
+                    mInterface.getPackageInstaller().getStagedSessions().getList();
+            printSessionList(pw, stagedSessions, sessionDump);
+        } catch (RemoteException e) {
+            pw.println("Failure ["
+                    + e.getClass().getName() + " - "
+                    + e.getMessage() + "]");
+            return -1;
+        }
+        return 1;
+    }
+
+    private void printSessionList(IndentingPrintWriter pw, List<SessionInfo> stagedSessions,
+            SessionDump sessionDump) {
+        final SparseArray<SessionInfo> sessionById = new SparseArray<>(stagedSessions.size());
+        for (SessionInfo session : stagedSessions) {
+            sessionById.put(session.getSessionId(), session);
+        }
+        for (SessionInfo session: stagedSessions) {
+            if (sessionDump.onlyReady && !session.isStagedSessionReady()) {
+                continue;
+            }
+            if (session.getParentSessionId() != SessionInfo.INVALID_ID) {
+                continue;
+            }
+            printSession(pw, session, sessionDump);
+            if (session.isMultiPackage() && !sessionDump.onlyParent) {
+                pw.increaseIndent();
+                final int[] childIds = session.getChildSessionIds();
+                for (int i = 0; i < childIds.length; i++) {
+                    final SessionInfo childSession = sessionById.get(childIds[i]);
+                    if (childSession == null) {
+                        if (sessionDump.onlySessionId) {
+                            pw.println(childIds[i]);
+                        } else {
+                            pw.println("sessionId = " + childIds[i] + "; not found");
+                        }
+                    } else {
+                        printSession(pw, childSession, sessionDump);
+                    }
+                }
+                pw.decreaseIndent();
+            }
+        }
+    }
+
+    private static void printSession(PrintWriter pw, SessionInfo session, SessionDump sessionDump) {
+        if (sessionDump.onlySessionId) {
+            pw.println(session.getSessionId());
+            return;
+        }
+        pw.println("sessionId = " + session.getSessionId()
+                + "; appPackageName = " + session.getAppPackageName()
+                + "; isStaged = " + session.isStaged()
+                + "; isReady = " + session.isStagedSessionReady()
+                + "; isApplied = " + session.isStagedSessionApplied()
+                + "; isFailed = " + session.isStagedSessionFailed() + ";");
+    }
+
     private Intent parseIntentAndUser() throws URISyntaxException {
         mTargetUser = UserHandle.USER_CURRENT;
         mBrief = false;
@@ -3108,6 +3160,12 @@
         pw.println("      -d: only list dangerous permissions");
         pw.println("      -u: list only the permissions users will see");
         pw.println("");
+        pw.println("  list staged-sessions [--only-ready] [--only-sessionid] [--only-parent]");
+        pw.println("    Displays list of all staged sessions on device.");
+        pw.println("      --only-ready: show only staged sessions that are ready");
+        pw.println("      --only-sessionid: show only sessionId of each session");
+        pw.println("      --only-parent: hide all children sessions");
+        pw.println("");
         pw.println("  resolve-activity [--brief] [--components] [--query-flags FLAGS]");
         pw.println("       [--user USER_ID] INTENT");
         pw.println("    Prints the activity that resolves to the given INTENT.");
@@ -3345,7 +3403,7 @@
         pw.println("  uninstall-system-updates");
         pw.println("    Remove updates to all system applications and fall back to their /system " +
                 "version.");
-        pw.println();
+        pw.println("");
         pw.println("  get-moduleinfo [--all | --installed] [module-name]");
         pw.println("    Displays module info. If module-name is specified only that info is shown");
         pw.println("    By default, without any argument only installed modules are shown.");
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 5972468..1873a4e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -62,6 +62,7 @@
 import android.os.Process;
 import android.os.SELinux;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -94,6 +95,7 @@
 import com.android.server.pm.permission.PermissionSettings;
 import com.android.server.pm.permission.PermissionsState;
 import com.android.server.pm.permission.PermissionsState.PermissionState;
+import com.android.server.utils.TimingsTraceAndSlog;
 
 import libcore.io.IoUtils;
 
@@ -4012,8 +4014,11 @@
         }
     }
 
-    void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
-            int userHandle, String[] disallowedPackages) {
+    void createNewUserLI(@NonNull PackageManagerService service,
+            @NonNull Installer installer, int userHandle, String[] disallowedPackages) {
+        final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
+                Trace.TRACE_TAG_PACKAGE_MANAGER);
+        t.traceBegin("createNewUser-" + userHandle);
         String[] volumeUuids;
         String[] names;
         int[] appIds;
@@ -4051,6 +4056,7 @@
                 targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
             }
         }
+        t.traceBegin("createAppData");
         for (int i = 0; i < packagesCount; i++) {
             if (names[i] == null) {
                 continue;
@@ -4064,9 +4070,11 @@
                 Slog.w(TAG, "Failed to prepare app data", e);
             }
         }
+        t.traceEnd(); // createAppData
         synchronized (mLock) {
             applyDefaultPreferredAppsLPw(userHandle);
         }
+        t.traceEnd(); // createNewUser
     }
 
     void removeUserLPw(int userId) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index dd1eb83..6b4ef69 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -39,6 +39,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -58,6 +60,7 @@
 import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -72,7 +75,7 @@
     private final PackageInstallerService mPi;
     private final ApexManager mApexManager;
     private final PowerManager mPowerManager;
-    private final Handler mBgHandler;
+    private final PreRebootVerificationHandler mPreRebootVerificationHandler;
 
     @GuardedBy("mStagedSessions")
     private final SparseArray<PackageInstallerSession> mStagedSessions = new SparseArray<>();
@@ -81,7 +84,8 @@
         mPi = pi;
         mApexManager = am;
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-        mBgHandler = BackgroundThread.getHandler();
+        mPreRebootVerificationHandler = new PreRebootVerificationHandler(
+                BackgroundThread.get().getLooper());
     }
 
     private void updateStoredSession(@NonNull PackageInstallerSession sessionInfo) {
@@ -150,6 +154,14 @@
             return;
         }
 
+        // Verify signing details for downgrade
+        // Allow downgrading from B to A iff it is possible to upgrade from A to B
+        if (existingApexPkg.getLongVersionCode() > newApexPkg.getLongVersionCode()
+                && existingSigningDetails.checkCapability(signingDetails,
+                        PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) {
+            return;
+        }
+
         throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
                 "APK-container signature of APEX package " + packageName + " with version "
                         + newApexPkg.versionCodeMajor + " and path " + apexPath + " is not"
@@ -241,75 +253,6 @@
         return (session.params.installFlags & PackageManager.INSTALL_APEX) != 0;
     }
 
-    private void preRebootVerification(@NonNull PackageInstallerSession session) {
-        Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
-        final boolean hasApex = sessionContainsApex(session);
-        // APEX checks. For single-package sessions, check if they contain an APEX. For
-        // multi-package sessions, find all the child sessions that contain an APEX.
-        if (hasApex) {
-            try {
-                final List<PackageInfo> apexPackages = submitSessionToApexService(session);
-                for (PackageInfo apexPackage : apexPackages) {
-                    validateApexSignature(apexPackage, session.params.installFlags);
-                }
-            } catch (PackageManagerException e) {
-                session.setStagedSessionFailed(e.error, e.getMessage());
-                return;
-            }
-        }
-
-        if (sessionContainsApk(session)) {
-            try {
-                Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
-                        + session.sessionId + " by performing a dry-run install");
-                installApksInSession(session, /* preReboot */ true);
-                // TODO(b/118865310): abort the session on apexd.
-            } catch (PackageManagerException e) {
-                session.setStagedSessionFailed(e.error, e.getMessage());
-                return;
-            }
-        }
-
-        if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-            // If rollback is enabled for this session, we call through to the RollbackManager
-            // with the list of sessions it must enable rollback for. Note that notifyStagedSession
-            // is a synchronous operation.
-            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
-            try {
-                // NOTE: To stay consistent with the non-staged install flow, we don't fail the
-                // entire install if rollbacks can't be enabled.
-                if (!rm.notifyStagedSession(session.sessionId)) {
-                    Slog.e(TAG, "Unable to enable rollback for session: " + session.sessionId);
-                }
-            } catch (RemoteException re) {
-                // Cannot happen, the rollback manager is in the same process.
-            }
-        }
-
-        // Proactively mark session as ready before calling apexd. Although this call order looks
-        // counter-intuitive, this is the easiest way to ensure that session won't end up in the
-        // inconsistent state:
-        //  - If device gets rebooted right before call to apexd, then apexd will never activate
-        //      apex files of this staged session. This will result in StagingManager failing the
-        //      session.
-        // On the other hand, if the order of the calls was inverted (first call apexd, then mark
-        // session as ready), then if a device gets rebooted right after the call to apexd, only
-        // apex part of the train will be applied, leaving device in an inconsistent state.
-        Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
-        session.setStagedSessionReady();
-        if (!hasApex) {
-            // Session doesn't contain apex, nothing to do.
-            return;
-        }
-        try {
-            mApexManager.markStagedSessionReady(session.sessionId);
-        } catch (PackageManagerException e) {
-            session.setStagedSessionFailed(e.error, e.getMessage());
-        }
-    }
-
-
     private boolean sessionContains(@NonNull PackageInstallerSession session,
                                     Predicate<PackageInstallerSession> filter) {
         if (!session.isMultiPackage()) {
@@ -358,7 +301,7 @@
                 // Greedily re-trigger the pre-reboot verification.
                 Slog.d(TAG, "Found pending staged session " + session.sessionId + " still to be "
                         + "verified, resuming pre-reboot verification");
-                mBgHandler.post(() -> preRebootVerification(session));
+                mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
                 return;
             }
             if (!apexSessionInfo.isActivated && !apexSessionInfo.isSuccess) {
@@ -375,7 +318,7 @@
         // The APEX part of the session is activated, proceed with the installation of APKs.
         try {
             Slog.d(TAG, "Installing APK packages in session " + session.sessionId);
-            installApksInSession(session, /* preReboot */ false);
+            installApksInSession(session);
         } catch (PackageManagerException e) {
             session.setStagedSessionFailed(e.error, e.getMessage());
 
@@ -467,53 +410,23 @@
         return apkSession;
     }
 
-    private void commitApkSession(@NonNull PackageInstallerSession apkSession,
-            int originalSessionId, boolean preReboot) throws PackageManagerException {
-        final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
-                : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
-        if (!preReboot) {
-            if ((apkSession.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
-                // If rollback is available for this session, notify the rollback
-                // manager of the apk session so it can properly enable rollback.
-                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
-                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
-                try {
-                    rm.notifyStagedApkSession(originalSessionId, apkSession.sessionId);
-                } catch (RemoteException re) {
-                    // Cannot happen, the rollback manager is in the same process.
-                }
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        apkSession.commit(receiver.getIntentSender(), false);
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status != PackageInstaller.STATUS_SUCCESS) {
-
-            final String errorMessage = result.getStringExtra(
-                    PackageInstaller.EXTRA_STATUS_MESSAGE);
-            Slog.e(TAG, "Failure to install APK staged session " + originalSessionId + " ["
-                    + errorMessage + "]");
-            throw new PackageManagerException(errorCode, errorMessage);
-        }
-    }
-
-    private void installApksInSession(@NonNull PackageInstallerSession session,
-                                         boolean preReboot) throws PackageManagerException {
+    /**
+     * Extract apks in the given session into a new session. Returns {@code null} if there is no
+     * apks in the given session. Only parent session is returned for multi-package session.
+     */
+    @Nullable
+    private PackageInstallerSession extractApksInSession(PackageInstallerSession session,
+            boolean preReboot) throws PackageManagerException {
         final int errorCode = preReboot ? SessionInfo.STAGED_SESSION_VERIFICATION_FAILED
                 : SessionInfo.STAGED_SESSION_ACTIVATION_FAILED;
         if (!session.isMultiPackage() && !isApexSession(session)) {
-            // APK single-packaged staged session. Do a regular install.
-            PackageInstallerSession apkSession = createAndWriteApkSession(session, preReboot);
-            commitApkSession(apkSession, session.sessionId, preReboot);
+            return createAndWriteApkSession(session, preReboot);
         } else if (session.isMultiPackage()) {
             // For multi-package staged sessions containing APKs, we identify which child sessions
             // contain an APK, and with those then create a new multi-package group of sessions,
             // carrying over all the session parameters and unmarking them as staged. On commit the
             // sessions will be installed atomically.
-            List<PackageInstallerSession> childSessions;
+            final List<PackageInstallerSession> childSessions;
             synchronized (mStagedSessions) {
                 childSessions =
                         Arrays.stream(session.getChildSessionIds())
@@ -525,18 +438,18 @@
             }
             if (childSessions.isEmpty()) {
                 // APEX-only multi-package staged session, nothing to do.
-                return;
+                return null;
             }
-            PackageInstaller.SessionParams params = session.params.copy();
+            final PackageInstaller.SessionParams params = session.params.copy();
             params.isStaged = false;
             if (preReboot) {
                 params.installFlags &= ~PackageManager.INSTALL_ENABLE_ROLLBACK;
             }
             // TODO(b/129744602): use the userid from the original session.
-            int apkParentSessionId = mPi.createSession(
+            final int apkParentSessionId = mPi.createSession(
                     params, session.getInstallerPackageName(),
                     0 /* UserHandle.SYSTEM */);
-            PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
+            final PackageInstallerSession apkParentSession = mPi.getSession(apkParentSessionId);
             try {
                 apkParentSession.open();
             } catch (IOException e) {
@@ -557,14 +470,80 @@
                             "Failed to add a child session " + apkChildSession.sessionId);
                 }
             }
-            commitApkSession(apkParentSession, session.sessionId, preReboot);
+            return apkParentSession;
         }
-        // APEX single-package staged session, nothing to do.
+        return null;
+    }
+
+    private void verifyApksInSession(PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToVerify = extractApksInSession(
+                session,  /* preReboot */ true);
+        if (apksToVerify == null) {
+            return;
+        }
+
+        final LocalIntentReceiverAsync receiver = new LocalIntentReceiverAsync(
+                (Intent result) -> {
+                    int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                            PackageInstaller.STATUS_FAILURE);
+                    if (status != PackageInstaller.STATUS_SUCCESS) {
+                        final String errorMessage = result.getStringExtra(
+                                PackageInstaller.EXTRA_STATUS_MESSAGE);
+                        Slog.e(TAG, "Failure to verify APK staged session "
+                                + session.sessionId + " [" + errorMessage + "]");
+                        session.setStagedSessionFailed(
+                                SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, errorMessage);
+                        return;
+                    }
+                    mPreRebootVerificationHandler.notifyPreRebootVerification_Apk_Complete(
+                            session.sessionId);
+                });
+
+        apksToVerify.commit(receiver.getIntentSender(), false);
+    }
+
+    private void installApksInSession(@NonNull PackageInstallerSession session)
+            throws PackageManagerException {
+
+        final PackageInstallerSession apksToInstall = extractApksInSession(
+                session, /* preReboot */ false);
+        if (apksToInstall == null) {
+            return;
+        }
+
+        if ((apksToInstall.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+            // If rollback is available for this session, notify the rollback
+            // manager of the apk session so it can properly enable rollback.
+            final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+            try {
+                rm.notifyStagedApkSession(session.sessionId, apksToInstall.sessionId);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Failed to notifyStagedApkSession for session: "
+                        + session.sessionId, re);
+            }
+        }
+
+        final LocalIntentReceiverSync receiver = new LocalIntentReceiverSync();
+        apksToInstall.commit(receiver.getIntentSender(), false);
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status != PackageInstaller.STATUS_SUCCESS) {
+            final String errorMessage = result.getStringExtra(
+                    PackageInstaller.EXTRA_STATUS_MESSAGE);
+            Slog.e(TAG, "Failure to install APK staged session "
+                    + session.sessionId + " [" + errorMessage + "]");
+            throw new PackageManagerException(
+                    SessionInfo.STAGED_SESSION_ACTIVATION_FAILED, errorMessage);
+        }
     }
 
     void commitSession(@NonNull PackageInstallerSession session) {
         updateStoredSession(session);
-        mBgHandler.post(() -> preRebootVerification(session));
+        mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
     }
 
     @Nullable
@@ -691,7 +670,7 @@
         if (!session.isStagedSessionReady()) {
             // The framework got restarted before the pre-reboot verification could complete,
             // restart the verification.
-            mBgHandler.post(() -> preRebootVerification(session));
+            mPreRebootVerificationHandler.startPreRebootVerification(session.sessionId);
         } else {
             // Session had already being marked ready. Start the checks to verify if there is any
             // follow-up work.
@@ -699,14 +678,34 @@
         }
     }
 
-    private static class LocalIntentReceiver {
+    private static class LocalIntentReceiverAsync {
+        final Consumer<Intent> mConsumer;
+
+        LocalIntentReceiverAsync(Consumer<Intent> consumer) {
+            mConsumer = consumer;
+        }
+
+        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                mConsumer.accept(intent);
+            }
+        };
+
+        public IntentSender getIntentSender() {
+            return new IntentSender((IIntentSender) mLocalSender);
+        }
+    }
+
+    private static class LocalIntentReceiverSync {
         private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
 
         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
             public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                             IIntentReceiver finishedReceiver, String requiredPermission,
-                             Bundle options) {
+                    IIntentReceiver finishedReceiver, String requiredPermission,
+                    Bundle options) {
                 try {
                     mResult.offer(intent, 5, TimeUnit.SECONDS);
                 } catch (InterruptedException e) {
@@ -727,4 +726,189 @@
             }
         }
     }
+
+    private final class PreRebootVerificationHandler extends Handler {
+
+        PreRebootVerificationHandler(Looper looper) {
+            super(looper);
+        }
+
+        /**
+         * Handler for states of pre reboot verification. The states are arranged linearly (shown
+         * below) with each state either calling the next state, or calling some other method that
+         * eventually calls the next state.
+         *
+         * <p><ul>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_START</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_APEX</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_APK</li>
+         *     <li>MSG_PRE_REBOOT_VERIFICATION_END</li>
+         * </ul></p>
+         *
+         * Details about each of state can be found in corresponding handler of node.
+         */
+        private static final int MSG_PRE_REBOOT_VERIFICATION_START = 1;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_APEX = 2;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_APK = 3;
+        private static final int MSG_PRE_REBOOT_VERIFICATION_END = 4;
+
+        @Override
+        public void handleMessage(Message msg) {
+            final int sessionId = msg.arg1;
+            final PackageInstallerSession session;
+            synchronized (mStagedSessions) {
+                session = mStagedSessions.get(sessionId);
+            }
+            // Maybe session was aborted before pre-reboot verification was complete
+            if (session == null) {
+                Slog.d(TAG, "Stopping pre-reboot verification for sessionId: " + sessionId);
+                return;
+            }
+            switch (msg.what) {
+                case MSG_PRE_REBOOT_VERIFICATION_START:
+                    handlePreRebootVerification_Start(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_APEX:
+                    handlePreRebootVerification_Apex(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_APK:
+                    handlePreRebootVerification_Apk(session);
+                    break;
+                case MSG_PRE_REBOOT_VERIFICATION_END:
+                    handlePreRebootVerification_End(session);
+                    break;
+            }
+        }
+
+        // Method for starting the pre-reboot verification
+        private void startPreRebootVerification(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Start_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APEX, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Apex_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_APK, sessionId, 0).sendToTarget();
+        }
+
+        private void notifyPreRebootVerification_Apk_Complete(int sessionId) {
+            obtainMessage(MSG_PRE_REBOOT_VERIFICATION_END, sessionId, 0).sendToTarget();
+        }
+
+        /**
+         * A dummy state for starting the pre reboot verification.
+         *
+         * See {@link PreRebootVerificationHandler} to see all nodes of pre reboot verification
+         */
+        private void handlePreRebootVerification_Start(@NonNull PackageInstallerSession session) {
+            Slog.d(TAG, "Starting preRebootVerification for session " + session.sessionId);
+            notifyPreRebootVerification_Start_Complete(session.sessionId);
+        }
+
+        /**
+         * Pre-reboot verification state for apex files:
+         *
+         * <p><ul>
+         *     <li>submits session to apex service</li>
+         *     <li>validates signatures of apex files</li>
+         * </ul></p>
+         */
+        private void handlePreRebootVerification_Apex(@NonNull PackageInstallerSession session) {
+            final boolean hasApex = sessionContainsApex(session);
+
+            // APEX checks. For single-package sessions, check if they contain an APEX. For
+            // multi-package sessions, find all the child sessions that contain an APEX.
+            if (hasApex) {
+                try {
+                    final List<PackageInfo> apexPackages =
+                            submitSessionToApexService(session);
+                    for (PackageInfo apexPackage : apexPackages) {
+                        validateApexSignature(
+                                apexPackage, session.params.installFlags);
+                    }
+                } catch (PackageManagerException e) {
+                    session.setStagedSessionFailed(e.error, e.getMessage());
+                    return;
+                }
+            }
+
+            notifyPreRebootVerification_Apex_Complete(session.sessionId);
+        }
+
+        /**
+         * Pre-reboot verification state for apk files:
+         *   <p><ul>
+         *       <li>performs a dry-run install of apk</li>
+         *   </ul></p>
+         */
+        private void handlePreRebootVerification_Apk(@NonNull PackageInstallerSession session) {
+            if (!sessionContainsApk(session)) {
+                notifyPreRebootVerification_Apk_Complete(session.sessionId);
+                return;
+            }
+
+            try {
+                Slog.d(TAG, "Running a pre-reboot verification for APKs in session "
+                        + session.sessionId + " by performing a dry-run install");
+
+                // verifyApksInSession will notify the handler when APK verification is complete
+                verifyApksInSession(session);
+                // TODO(b/118865310): abort the session on apexd.
+            } catch (PackageManagerException e) {
+                session.setStagedSessionFailed(e.error, e.getMessage());
+            }
+        }
+
+        /**
+         * Pre-reboot verification state for wrapping up:
+         * <p><ul>
+         *     <li>enables rollback if required</li>
+         *     <li>marks session as ready</li>
+         * </ul></p>
+         */
+        private void handlePreRebootVerification_End(@NonNull PackageInstallerSession session) {
+            if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
+                // If rollback is enabled for this session, we call through to the RollbackManager
+                // with the list of sessions it must enable rollback for. Note that
+                // notifyStagedSession is a synchronous operation.
+                final IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                        ServiceManager.getService(Context.ROLLBACK_SERVICE));
+                try {
+                    // NOTE: To stay consistent with the non-staged install flow, we don't fail the
+                    // entire install if rollbacks can't be enabled.
+                    if (!rm.notifyStagedSession(session.sessionId)) {
+                        Slog.e(TAG, "Unable to enable rollback for session: "
+                                + session.sessionId);
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Failed to notifyStagedSession for session: "
+                            + session.sessionId, re);
+                }
+            }
+
+            // Proactively mark session as ready before calling apexd. Although this call order
+            // looks counter-intuitive, this is the easiest way to ensure that session won't end up
+            // in the inconsistent state:
+            //  - If device gets rebooted right before call to apexd, then apexd will never activate
+            //      apex files of this staged session. This will result in StagingManager failing
+            //      the session.
+            // On the other hand, if the order of the calls was inverted (first call apexd, then
+            // mark session as ready), then if a device gets rebooted right after the call to apexd,
+            // only apex part of the train will be applied, leaving device in an inconsistent state.
+            Slog.d(TAG, "Marking session " + session.sessionId + " as ready");
+            session.setStagedSessionReady();
+            final boolean hasApex = sessionContainsApex(session);
+            if (!hasApex) {
+                // Session doesn't contain apex, nothing to do.
+                return;
+            }
+            try {
+                mApexManager.markStagedSessionReady(session.sessionId);
+            } catch (PackageManagerException e) {
+                session.setStagedSessionFailed(e.error, e.getMessage());
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 65fb35d..9371c44 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -404,10 +404,10 @@
                 return;
             }
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-            final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
             // Call setQuietModeEnabled on bg thread to avoid ANR
             BackgroundThread.getHandler().post(() ->
-                    setQuietModeEnabled(userHandle, false, target, /* callingPackage */ null));
+                    setQuietModeEnabled(userId, false, target, /* callingPackage */ null));
         }
     };
 
@@ -482,9 +482,9 @@
         }
 
         @Override
-        public void onStartUser(int userHandle) {
+        public void onStartUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.startRealtime = SystemClock.elapsedRealtime();
                 }
@@ -492,9 +492,9 @@
         }
 
         @Override
-        public void onUnlockUser(int userHandle) {
+        public void onUnlockUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.unlockRealtime = SystemClock.elapsedRealtime();
                 }
@@ -502,9 +502,9 @@
         }
 
         @Override
-        public void onStopUser(int userHandle) {
+        public void onStopUser(@UserIdInt int userId) {
             synchronized (mUms.mUsersLock) {
-                final UserData user = mUms.getUserDataLU(userHandle);
+                final UserData user = mUms.getUserDataLU(userId);
                 if (user != null) {
                     user.startRealtime = 0;
                     user.unlockRealtime = 0;
@@ -610,7 +610,7 @@
     }
 
     @Override
-    public String getUserAccount(int userId) {
+    public String getUserAccount(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("get user account");
         synchronized (mUsersLock) {
             return mUsers.get(userId).account;
@@ -618,7 +618,7 @@
     }
 
     @Override
-    public void setUserAccount(int userId, String accountName) {
+    public void setUserAccount(@UserIdInt int userId, String accountName) {
         checkManageUserAndAcrossUsersFullPermission("set user account");
         UserData userToUpdate = null;
         synchronized (mPackagesLock) {
@@ -676,7 +676,7 @@
     }
 
     @Override
-    public List<UserInfo> getProfiles(int userId, boolean enabledOnly) {
+    public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) {
         boolean returnFullInfo = true;
         if (userId != UserHandle.getCallingUserId()) {
             checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
@@ -694,7 +694,7 @@
     }
 
     @Override
-    public int[] getProfileIds(int userId, boolean enabledOnly) {
+    public int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
         if (userId != UserHandle.getCallingUserId()) {
             checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
         }
@@ -710,7 +710,8 @@
 
     /** Assume permissions already checked and caller's identity cleared */
     @GuardedBy("mUsersLock")
-    private List<UserInfo> getProfilesLU(int userId, boolean enabledOnly, boolean fullInfo) {
+    private List<UserInfo> getProfilesLU(@UserIdInt int userId, boolean enabledOnly,
+            boolean fullInfo) {
         IntArray profileIds = getProfileIdsLU(userId, enabledOnly);
         ArrayList<UserInfo> users = new ArrayList<>(profileIds.size());
         for (int i = 0; i < profileIds.size(); i++) {
@@ -733,7 +734,7 @@
      *  Assume permissions already checked and caller's identity cleared
      */
     @GuardedBy("mUsersLock")
-    private IntArray getProfileIdsLU(int userId, boolean enabledOnly) {
+    private IntArray getProfileIdsLU(@UserIdInt int userId, boolean enabledOnly) {
         UserInfo user = getUserInfoLU(userId);
         IntArray result = new IntArray(mUsers.size());
         if (user == null) {
@@ -761,28 +762,28 @@
     }
 
     @Override
-    public int getCredentialOwnerProfile(int userHandle) {
+    public int getCredentialOwnerProfile(@UserIdInt int userId) {
         checkManageUsersPermission("get the credential owner");
-        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
+        if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
             synchronized (mUsersLock) {
-                UserInfo profileParent = getProfileParentLU(userHandle);
+                UserInfo profileParent = getProfileParentLU(userId);
                 if (profileParent != null) {
                     return profileParent.id;
                 }
             }
         }
 
-        return userHandle;
+        return userId;
     }
 
     @Override
-    public boolean isSameProfileGroup(int userId, int otherUserId) {
+    public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
         if (userId == otherUserId) return true;
         checkManageUsersPermission("check if in the same profile group");
         return isSameProfileGroupNoChecks(userId, otherUserId);
     }
 
-    private boolean isSameProfileGroupNoChecks(int userId, int otherUserId) {
+    private boolean isSameProfileGroupNoChecks(@UserIdInt int userId, int otherUserId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
@@ -798,27 +799,27 @@
     }
 
     @Override
-    public UserInfo getProfileParent(int userHandle) {
+    public UserInfo getProfileParent(@UserIdInt int userId) {
         checkManageUsersPermission("get the profile parent");
         synchronized (mUsersLock) {
-            return getProfileParentLU(userHandle);
+            return getProfileParentLU(userId);
         }
     }
 
     @Override
-    public int getProfileParentId(int userHandle) {
+    public int getProfileParentId(@UserIdInt int userId) {
         checkManageUsersPermission("get the profile parent");
-        return mLocalService.getProfileParentId(userHandle);
+        return mLocalService.getProfileParentId(userId);
     }
 
     @GuardedBy("mUsersLock")
-    private UserInfo getProfileParentLU(int userHandle) {
-        UserInfo profile = getUserInfoLU(userHandle);
+    private UserInfo getProfileParentLU(@UserIdInt int userId) {
+        UserInfo profile = getUserInfoLU(userId);
         if (profile == null) {
             return null;
         }
         int parentUserId = profile.profileGroupId;
-        if (parentUserId == userHandle || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
+        if (parentUserId == userId || parentUserId == UserInfo.NO_PROFILE_GROUP_ID) {
             return null;
         } else {
             return getUserInfoLU(parentUserId);
@@ -848,7 +849,7 @@
 
     @Override
     public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
-            int userHandle, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target) {
         Preconditions.checkNotNull(callingPackage);
 
         if (enableQuietMode && target != null) {
@@ -862,17 +863,17 @@
             boolean result = false;
             if (enableQuietMode) {
                 setQuietModeEnabled(
-                        userHandle, true /* enableQuietMode */, target, callingPackage);
+                        userId, true /* enableQuietMode */, target, callingPackage);
                 result = true;
             } else {
                 boolean needToShowConfirmCredential =
-                        mLockPatternUtils.isSecure(userHandle)
-                                && !StorageManager.isUserKeyUnlocked(userHandle);
+                        mLockPatternUtils.isSecure(userId)
+                                && !StorageManager.isUserKeyUnlocked(userId);
                 if (needToShowConfirmCredential) {
-                    showConfirmCredentialToDisableQuietMode(userHandle, target);
+                    showConfirmCredentialToDisableQuietMode(userId, target);
                 } else {
                     setQuietModeEnabled(
-                            userHandle, false /* enableQuietMode */, target, callingPackage);
+                            userId, false /* enableQuietMode */, target, callingPackage);
                     result = true;
                 }
             }
@@ -922,16 +923,16 @@
                 + "default launcher nor has MANAGE_USERS/MODIFY_QUIET_MODE permission");
     }
 
-    private void setQuietModeEnabled(int userHandle, boolean enableQuietMode,
+    private void setQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             IntentSender target, @Nullable String callingPackage) {
         final UserInfo profile, parent;
         final UserData profileUserData;
         synchronized (mUsersLock) {
-            profile = getUserInfoLU(userHandle);
-            parent = getProfileParentLU(userHandle);
+            profile = getUserInfoLU(userId);
+            parent = getProfileParentLU(userId);
 
             if (profile == null || !profile.isManagedProfile()) {
-                throw new IllegalArgumentException("User " + userHandle + " is not a profile");
+                throw new IllegalArgumentException("User " + userId + " is not a profile");
             }
             if (profile.isQuietModeEnabled() == enableQuietMode) {
                 Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
@@ -945,17 +946,17 @@
         }
         try {
             if (enableQuietMode) {
-                ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+                ActivityManager.getService().stopUser(userId, /* force */true, null);
                 LocalServices.getService(ActivityManagerInternal.class)
-                        .killForegroundAppsForUser(userHandle);
+                        .killForegroundAppsForUser(userId);
             } else {
                 IProgressListener callback = target != null
                         ? new DisableQuietModeUserUnlockedCallback(target)
                         : null;
                 ActivityManager.getService().startUserInBackgroundWithListener(
-                        userHandle, callback);
+                        userId, callback);
             }
-            logQuietModeEnabled(userHandle, enableQuietMode, callingPackage);
+            logQuietModeEnabled(userId, enableQuietMode, callingPackage);
         } catch (RemoteException e) {
             // Should not happen, same process.
             e.rethrowAsRuntimeException();
@@ -964,11 +965,11 @@
                 enableQuietMode);
     }
 
-    private void logQuietModeEnabled(int userHandle, boolean enableQuietMode,
+    private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
             @Nullable String callingPackage) {
         UserData userData;
         synchronized (mUsersLock) {
-            userData = getUserDataLU(userHandle);
+            userData = getUserDataLU(userId);
         }
         if (userData == null) {
             return;
@@ -987,11 +988,11 @@
     }
 
     @Override
-    public boolean isQuietModeEnabled(int userHandle) {
+    public boolean isQuietModeEnabled(@UserIdInt int userId) {
         synchronized (mPackagesLock) {
             UserInfo info;
             synchronized (mUsersLock) {
-                info = getUserInfoLU(userHandle);
+                info = getUserInfoLU(userId);
             }
             if (info == null || !info.isManagedProfile()) {
                 return false;
@@ -1004,15 +1005,14 @@
      * Show confirm credential screen to unlock user in order to turn off quiet mode.
      */
     private void showConfirmCredentialToDisableQuietMode(
-            @UserIdInt int userHandle, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target) {
         // otherwise, we show a profile challenge to trigger decryption of the user
         final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
                 Context.KEYGUARD_SERVICE);
-        // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+        // We should use userId not credentialOwnerUserId here, as even if it is unified
         // lock, confirm screenlock page will know and show personal challenge, and unlock
         // work profile when personal challenge is correct
-        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
-                userHandle);
+        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
         if (unlockIntent == null) {
             return;
         }
@@ -1021,7 +1021,7 @@
         if (target != null) {
             callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
         callBackIntent.setPackage(mContext.getPackageName());
         callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
@@ -1039,7 +1039,7 @@
     }
 
     @Override
-    public void setUserEnabled(int userId) {
+    public void setUserEnabled(@UserIdInt int userId) {
         checkManageUsersPermission("enable user");
         synchronized (mPackagesLock) {
             UserInfo info;
@@ -1054,7 +1054,7 @@
     }
 
     @Override
-    public void setUserAdmin(int userId) {
+    public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
 
         synchronized (mPackagesLock) {
@@ -1097,7 +1097,7 @@
     }
 
     @Override
-    public UserInfo getUserInfo(int userId) {
+    public UserInfo getUserInfo(@UserIdInt int userId) {
         checkManageOrCreateUsersPermission("query user");
         synchronized (mUsersLock) {
             return userWithName(getUserInfoLU(userId));
@@ -1128,7 +1128,7 @@
     }
 
     @Override
-    public boolean isManagedProfile(int userId) {
+    public boolean isManagedProfile(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isManagedProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
@@ -1137,19 +1137,19 @@
     }
 
     @Override
-    public boolean isUserUnlockingOrUnlocked(int userId) {
+    public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked");
         return mLocalService.isUserUnlockingOrUnlocked(userId);
     }
 
     @Override
-    public boolean isUserUnlocked(int userId) {
+    public boolean isUserUnlocked(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
         return mLocalService.isUserUnlocked(userId);
     }
 
     @Override
-    public boolean isUserRunning(int userId) {
+    public boolean isUserRunning(@UserIdInt int userId) {
         checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning");
         return mLocalService.isUserRunning(userId);
     }
@@ -1190,7 +1190,8 @@
         }
     }
 
-    private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
+    private void checkManageOrInteractPermIfCallerInOtherProfileGroup(@UserIdInt int userId,
+            String name) {
         int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
                 hasManageUsersPermission()) {
@@ -1204,7 +1205,7 @@
     }
 
     @Override
-    public boolean isDemoUser(int userId) {
+    public boolean isDemoUser(@UserIdInt int userId) {
         int callingUserId = UserHandle.getCallingUserId();
         if (callingUserId != userId && !hasManageUsersPermission()) {
             throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
@@ -1224,7 +1225,7 @@
     }
 
     @Override
-    public boolean canHaveRestrictedProfile(int userId) {
+    public boolean canHaveRestrictedProfile(@UserIdInt int userId) {
         checkManageUsersPermission("canHaveRestrictedProfile");
         synchronized (mUsersLock) {
             final UserInfo userInfo = getUserInfoLU(userId);
@@ -1260,7 +1261,7 @@
      * Should be locked on mUsers before calling this.
      */
     @GuardedBy("mUsersLock")
-    private UserInfo getUserInfoLU(int userId) {
+    private UserInfo getUserInfoLU(@UserIdInt int userId) {
         final UserData userData = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
         if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
@@ -1271,7 +1272,7 @@
     }
 
     @GuardedBy("mUsersLock")
-    private UserData getUserDataLU(int userId) {
+    private UserData getUserDataLU(@UserIdInt int userId) {
         final UserData userData = mUsers.get(userId);
         // If it is partial and not in the process of being removed, return as unknown user.
         if (userData != null && userData.info.partial && !mRemovingUserIds.get(userId)) {
@@ -1284,7 +1285,7 @@
      * Obtains {@link #mUsersLock} and return UserInfo from mUsers.
      * <p>No permissions checking or any addition checks are made</p>
      */
-    private UserInfo getUserInfoNoChecks(int userId) {
+    private UserInfo getUserInfoNoChecks(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             final UserData userData = mUsers.get(userId);
             return userData != null ? userData.info : null;
@@ -1295,19 +1296,19 @@
      * Obtains {@link #mUsersLock} and return UserData from mUsers.
      * <p>No permissions checking or any addition checks are made</p>
      */
-    private UserData getUserDataNoChecks(int userId) {
+    private UserData getUserDataNoChecks(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             return mUsers.get(userId);
         }
     }
 
     /** Called by PackageManagerService */
-    public boolean exists(int userId) {
+    public boolean exists(@UserIdInt int userId) {
         return mLocalService.exists(userId);
     }
 
     @Override
-    public void setUserName(int userId, String name) {
+    public void setUserName(@UserIdInt int userId, String name) {
         checkManageUsersPermission("rename users");
         boolean changed = false;
         synchronized (mPackagesLock) {
@@ -1333,7 +1334,7 @@
     }
 
     @Override
-    public void setUserIcon(int userId, Bitmap bitmap) {
+    public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
         checkManageUsersPermission("update users");
         if (hasUserRestriction(UserManager.DISALLOW_SET_USER_ICON, userId)) {
             Log.w(LOG_TAG, "Cannot set user icon. DISALLOW_SET_USER_ICON is enabled.");
@@ -1344,7 +1345,7 @@
 
 
 
-    private void sendUserInfoChangedBroadcast(int userId) {
+    private void sendUserInfoChangedBroadcast(@UserIdInt int userId) {
         Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
         changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1389,7 +1390,7 @@
         return null;
     }
 
-    public void makeInitialized(int userId) {
+    public void makeInitialized(@UserIdInt int userId) {
         checkManageUsersPermission("makeInitialized");
         boolean scheduleWriteUser = false;
         UserData userData;
@@ -1447,8 +1448,8 @@
     /**
      * See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
      */
-    private void setDevicePolicyUserRestrictionsInner(int userId, @Nullable Bundle restrictions,
-            boolean isDeviceOwner, int cameraRestrictionScope) {
+    private void setDevicePolicyUserRestrictionsInner(@UserIdInt int userId,
+            @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
         final Bundle global = new Bundle();
         final Bundle local = new Bundle();
 
@@ -1505,8 +1506,8 @@
      * empty, record is removed from the array.
      * @return whether restrictions bundle is different from the old one.
      */
-    private boolean updateRestrictionsIfNeededLR(int userId, @Nullable Bundle restrictions,
-            SparseArray<Bundle> restrictionsArray) {
+    private boolean updateRestrictionsIfNeededLR(@UserIdInt int userId,
+            @Nullable Bundle restrictions, SparseArray<Bundle> restrictionsArray) {
         final boolean changed =
                 !UserRestrictionsUtils.areEqual(restrictionsArray.get(userId), restrictions);
         if (changed) {
@@ -1520,7 +1521,7 @@
     }
 
     @GuardedBy("mRestrictionsLock")
-    private Bundle computeEffectiveUserRestrictionsLR(int userId) {
+    private Bundle computeEffectiveUserRestrictionsLR(@UserIdInt int userId) {
         final Bundle baseRestrictions =
                 UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
         final Bundle global = UserRestrictionsUtils.mergeAll(mDevicePolicyGlobalUserRestrictions);
@@ -1538,14 +1539,14 @@
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void invalidateEffectiveUserRestrictionsLR(int userId) {
+    private void invalidateEffectiveUserRestrictionsLR(@UserIdInt int userId) {
         if (DBG) {
             Log.d(LOG_TAG, "invalidateEffectiveUserRestrictions userId=" + userId);
         }
         mCachedEffectiveUserRestrictions.remove(userId);
     }
 
-    private Bundle getEffectiveUserRestrictions(int userId) {
+    private Bundle getEffectiveUserRestrictions(@UserIdInt int userId) {
         synchronized (mRestrictionsLock) {
             Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
             if (restrictions == null) {
@@ -1558,7 +1559,7 @@
 
     /** @return a specific user restriction that's in effect currently. */
     @Override
-    public boolean hasUserRestriction(String restrictionKey, int userId) {
+    public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
         return mLocalService.hasUserRestriction(restrictionKey, userId);
     }
 
@@ -1593,7 +1594,7 @@
      *         and {@link UserManager#RESTRICTION_SOURCE_PROFILE_OWNER}
      */
     @Override
-    public int getUserRestrictionSource(String restrictionKey, int userId) {
+    public int getUserRestrictionSource(String restrictionKey, @UserIdInt int userId) {
         List<EnforcingUser> enforcingUsers = getUserRestrictionSources(restrictionKey,  userId);
         // Get "bitwise or" of restriction sources for all enforcing users.
         int result = UserManager.RESTRICTION_NOT_SET;
@@ -1652,12 +1653,12 @@
      * {@link Bundle}.
      */
     @Override
-    public Bundle getUserRestrictions(int userId) {
+    public Bundle getUserRestrictions(@UserIdInt int userId) {
         return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
-    public boolean hasBaseUserRestriction(String restrictionKey, int userId) {
+    public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) {
         checkManageUsersPermission("hasBaseUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
             return false;
@@ -1669,7 +1670,7 @@
     }
 
     @Override
-    public void setUserRestriction(String key, boolean value, int userId) {
+    public void setUserRestriction(String key, boolean value, @UserIdInt int userId) {
         checkManageUsersPermission("setUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(key)) {
             return;
@@ -1695,7 +1696,7 @@
      */
     @GuardedBy("mRestrictionsLock")
     private void updateUserRestrictionsInternalLR(
-            @Nullable Bundle newBaseRestrictions, int userId) {
+            @Nullable Bundle newBaseRestrictions, @UserIdInt int userId) {
         final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
                 mAppliedUserRestrictions.get(userId));
 
@@ -1779,7 +1780,7 @@
 
     // Package private for the inner class.
     @GuardedBy("mRestrictionsLock")
-    void applyUserRestrictionsLR(int userId) {
+    void applyUserRestrictionsLR(@UserIdInt int userId) {
         updateUserRestrictionsInternalLR(null, userId);
     }
 
@@ -1831,7 +1832,7 @@
     }
 
     @Override
-    public boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne) {
+    public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
         checkManageUsersPermission("check if more managed profiles can be added.");
         if (ActivityManager.isLowRamDeviceStatic()) {
             return false;
@@ -2252,10 +2253,11 @@
 
     @GuardedBy({"mPackagesLock", "mRestrictionsLock"})
     private void fallbackToSingleUserLP() {
-        int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN;
-        // In headless system user mode, the primary flag is assigned to the first human user.
+        int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
+                | UserInfo.FLAG_PRIMARY;
+        // In headless system user mode, headless system user is not a full user.
         if (!UserManager.isHeadlessSystemUserMode()) {
-            flags |= UserInfo.FLAG_PRIMARY | UserInfo.FLAG_FULL;
+            flags |= UserInfo.FLAG_FULL;
         }
         // Create the system user
         UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags);
@@ -2661,7 +2663,7 @@
     /**
      * Removes the app restrictions file for a specific package and user id, if it exists.
      */
-    private static void cleanAppRestrictionsForPackageLAr(String pkg, int userId) {
+    private static void cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
         File dir = Environment.getUserSystemDirectory(userId);
         File resFile = new File(dir, packageToRestrictionsFileName(pkg));
         if (resFile.exists()) {
@@ -2670,23 +2672,23 @@
     }
 
     @Override
-    public UserInfo createProfileForUser(String name, int flags, int userId,
+    public UserInfo createProfileForUser(String name, int flags, @UserIdInt int userId,
             String[] disallowedPackages) {
         checkManageOrCreateUsersPermission(flags);
         return createUserInternal(name, flags, userId, disallowedPackages);
     }
 
     @Override
-    public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags, int userId,
-            String[] disallowedPackages) {
+    public UserInfo createProfileForUserEvenWhenDisallowed(String name, int flags,
+            @UserIdInt int userId, String[] disallowedPackages) {
         checkManageOrCreateUsersPermission(flags);
         return createUserInternalUnchecked(name, flags, userId, disallowedPackages);
     }
 
     @Override
-    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userHandle) {
+    public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
         checkManageOrCreateUsersPermission("Only the system can remove users");
-        return removeUserUnchecked(userHandle);
+        return removeUserUnchecked(userId);
     }
 
     @Override
@@ -2711,14 +2713,25 @@
         return createUserInternalUnchecked(name, flags, parentId, disallowedPackages);
     }
 
-    private UserInfo createUserInternalUnchecked(String name, int flags, int parentId,
-            String[] disallowedPackages) {
+    private UserInfo createUserInternalUnchecked(@Nullable String name, int flags,
+            int parentId, @Nullable String[] disallowedPackages) {
+        TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+        t.traceBegin("createUser");
+        UserInfo userInfo =
+                createUserInternalUncheckedNoTracing(name, flags, parentId, disallowedPackages, t);
+        t.traceEnd();
+        return userInfo;
+    }
+
+    private UserInfo createUserInternalUncheckedNoTracing(@Nullable String name, int flags,
+            int parentId, @Nullable String[] disallowedPackages, @NonNull TimingsTraceAndSlog t) {
         DeviceStorageMonitorInternal dsm = LocalServices
                 .getService(DeviceStorageMonitorInternal.class);
         if (dsm.isMemoryLow()) {
             Log.w(LOG_TAG, "Cannot add user. Not enough space on disk.");
             return null;
         }
+
         final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
         final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
         final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
@@ -2769,17 +2782,7 @@
                         return null;
                     }
                 }
-                // In headless system user mode, we assign the first human user the primary flag.
-                // And if there is no device owner, we also assign the admin flag to primary user.
-                if (UserManager.isHeadlessSystemUserMode()
-                        && !isGuest && !isManagedProfile && getPrimaryUser() == null) {
-                    flags |= UserInfo.FLAG_PRIMARY;
-                    synchronized (mUsersLock) {
-                        if (!mIsDeviceManaged) {
-                            flags |= UserInfo.FLAG_ADMIN;
-                        }
-                    }
-                }
+
                 if (!isManagedProfile) {
                     // New users cannot be system, and it's not a profile, so per-force it's FULL.
                     flags |= UserInfo.FLAG_FULL;
@@ -2828,11 +2831,21 @@
                     }
                 }
             }
+
+            t.traceBegin("createUserKey");
             final StorageManager storage = mContext.getSystemService(StorageManager.class);
             storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
+            t.traceEnd();
+
+            t.traceBegin("prepareUserData");
             mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
                     StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+            t.traceEnd();
+
+            t.traceBegin("PM.createNewUser");
             mPm.createNewUser(userId, disallowedPackages);
+            t.traceEnd();
+
             userInfo.partial = false;
             synchronized (mPackagesLock) {
                 writeUserLP(userData);
@@ -2847,7 +2860,11 @@
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.append(userId, restrictions);
             }
+
+            t.traceBegin("PM.onNewUserCreated");
             mPm.onNewUserCreated(userId);
+            t.traceEnd();
+
             Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
             addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
@@ -2871,7 +2888,7 @@
     }
 
     @VisibleForTesting
-    void removeUserInfo(int userId) {
+    void removeUserInfo(@UserIdInt int userId) {
         synchronized (mUsers) {
             mUsers.remove(userId);
         }
@@ -2923,11 +2940,11 @@
     /**
      * Mark this guest user for deletion to allow us to create another guest
      * and switch to that user before actually removing this guest.
-     * @param userHandle the userid of the current guest
+     * @param userId the userid of the current guest
      * @return whether the user could be marked for deletion
      */
     @Override
-    public boolean markGuestForDeletion(int userHandle) {
+    public boolean markGuestForDeletion(@UserIdInt int userId) {
         checkManageUsersPermission("Only the system can remove users");
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(
                 UserManager.DISALLOW_REMOVE_USER, false)) {
@@ -2940,8 +2957,8 @@
             final UserData userData;
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    userData = mUsers.get(userHandle);
-                    if (userHandle == 0 || userData == null || mRemovingUserIds.get(userHandle)) {
+                    userData = mUsers.get(userId);
+                    if (userId == 0 || userData == null || mRemovingUserIds.get(userId)) {
                         return false;
                     }
                 }
@@ -2968,16 +2985,16 @@
     /**
      * Removes a user and all data directories created for that user. This method should be called
      * after the user's processes have been terminated.
-     * @param userHandle the user's id
+     * @param userId the user's id
      */
     @Override
-    public boolean removeUser(int userHandle) {
-        Slog.i(LOG_TAG, "removeUser u" + userHandle);
+    public boolean removeUser(@UserIdInt int userId) {
+        Slog.i(LOG_TAG, "removeUser u" + userId);
         checkManageOrCreateUsersPermission("Only the system can remove users");
 
         final boolean isManagedProfile;
         synchronized (mUsersLock) {
-            UserInfo userInfo = getUserInfoLU(userHandle);
+            UserInfo userInfo = getUserInfoLU(userId);
             isManagedProfile = userInfo != null && userInfo.isManagedProfile();
         }
         String restriction = isManagedProfile
@@ -2986,39 +3003,39 @@
             Log.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
             return false;
         }
-        return removeUserUnchecked(userHandle);
+        return removeUserUnchecked(userId);
     }
 
-    private boolean removeUserUnchecked(int userHandle) {
+    private boolean removeUserUnchecked(@UserIdInt int userId) {
         long ident = Binder.clearCallingIdentity();
         try {
             final UserData userData;
             int currentUser = ActivityManager.getCurrentUser();
-            if (currentUser == userHandle) {
+            if (currentUser == userId) {
                 Log.w(LOG_TAG, "Current user cannot be removed.");
                 return false;
             }
             synchronized (mPackagesLock) {
                 synchronized (mUsersLock) {
-                    userData = mUsers.get(userHandle);
-                    if (userHandle == UserHandle.USER_SYSTEM) {
+                    userData = mUsers.get(userId);
+                    if (userId == UserHandle.USER_SYSTEM) {
                         Log.e(LOG_TAG, "System user cannot be removed.");
                         return false;
                     }
 
                     if (userData == null) {
                         Log.e(LOG_TAG, String.format(
-                                "Cannot remove user %d, invalid user id provided.", userHandle));
+                                "Cannot remove user %d, invalid user id provided.", userId));
                         return false;
                     }
 
-                    if (mRemovingUserIds.get(userHandle)) {
+                    if (mRemovingUserIds.get(userId)) {
                         Log.e(LOG_TAG, String.format(
-                                "User %d is already scheduled for removal.", userHandle));
+                                "User %d is already scheduled for removal.", userId));
                         return false;
                     }
 
-                    addRemovingUserIdLocked(userHandle);
+                    addRemovingUserIdLocked(userId);
                 }
 
                 // Set this to a partially created user, so that the user will be purged
@@ -3031,7 +3048,7 @@
                 writeUserLP(userData);
             }
             try {
-                mAppOpsService.removeUser(userHandle);
+                mAppOpsService.removeUser(userId);
             } catch (RemoteException e) {
                 Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user.", e);
             }
@@ -3043,17 +3060,17 @@
                 sendProfileRemovedBroadcast(userData.info.profileGroupId, userData.info.id);
             }
 
-            if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
+            if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
             int res;
             try {
-                res = ActivityManager.getService().stopUser(userHandle, /* force= */ true,
+                res = ActivityManager.getService().stopUser(userId, /* force= */ true,
                 new IStopUserCallback.Stub() {
                             @Override
-                            public void userStopped(int userId) {
-                                finishRemoveUser(userId);
+                            public void userStopped(int userIdParam) {
+                                finishRemoveUser(userIdParam);
                             }
                             @Override
-                            public void userStopAborted(int userId) {
+                            public void userStopAborted(int userIdParam) {
                             }
                         });
             } catch (RemoteException e) {
@@ -3068,7 +3085,7 @@
 
     @GuardedBy("mUsersLock")
     @VisibleForTesting
-    void addRemovingUserIdLocked(int userId) {
+    void addRemovingUserIdLocked(@UserIdInt int userId) {
         // We remember deleted user IDs to prevent them from being
         // reused during the current boot; they can still be reused
         // after a reboot or recycling of userIds.
@@ -3080,14 +3097,14 @@
         }
     }
 
-    void finishRemoveUser(final int userHandle) {
-        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
+    void finishRemoveUser(final @UserIdInt int userId) {
+        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userId);
         // Let other services shutdown any activity and clean up their state before completely
         // wiping the user's system directory and removing from the user list
         long ident = Binder.clearCallingIdentity();
         try {
             Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
-            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
             mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
                     android.Manifest.permission.MANAGE_USERS,
 
@@ -3097,15 +3114,15 @@
                             if (DBG) {
                                 Slog.i(LOG_TAG,
                                         "USER_REMOVED broadcast sent, cleaning up user data "
-                                        + userHandle);
+                                        + userId);
                             }
                             new Thread() {
                                 @Override
                                 public void run() {
                                     // Clean up any ActivityTaskManager state
                                     LocalServices.getService(ActivityTaskManagerInternal.class)
-                                            .onUserStopped(userHandle);
-                                    removeUserState(userHandle);
+                                            .onUserStopped(userId);
+                                    removeUserState(userId);
                                 }
                             }.start();
                         }
@@ -3117,47 +3134,46 @@
         }
     }
 
-    private void removeUserState(final int userHandle) {
+    private void removeUserState(final @UserIdInt int userId) {
         try {
-            mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
+            mContext.getSystemService(StorageManager.class).destroyUserKey(userId);
         } catch (IllegalStateException e) {
             // This may be simply because the user was partially created.
-            Slog.i(LOG_TAG,
-                "Destroying key for user " + userHandle + " failed, continuing anyway", e);
+            Slog.i(LOG_TAG, "Destroying key for user " + userId + " failed, continuing anyway", e);
         }
 
         // Cleanup gatekeeper secure user id
         try {
             final IGateKeeperService gk = GateKeeper.getService();
             if (gk != null) {
-                gk.clearSecureUserId(userHandle);
+                gk.clearSecureUserId(userId);
             }
         } catch (Exception ex) {
             Slog.w(LOG_TAG, "unable to clear GK secure user id");
         }
 
         // Cleanup package manager settings
-        mPm.cleanUpUser(this, userHandle);
+        mPm.cleanUpUser(this, userId);
 
         // Clean up all data before removing metadata
-        mUserDataPreparer.destroyUserData(userHandle,
+        mUserDataPreparer.destroyUserData(userId,
                 StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
 
         // Remove this user from the list
         synchronized (mUsersLock) {
-            mUsers.remove(userHandle);
-            mIsUserManaged.delete(userHandle);
+            mUsers.remove(userId);
+            mIsUserManaged.delete(userId);
         }
         synchronized (mUserStates) {
-            mUserStates.delete(userHandle);
+            mUserStates.delete(userId);
         }
         synchronized (mRestrictionsLock) {
-            mBaseUserRestrictions.remove(userHandle);
-            mAppliedUserRestrictions.remove(userHandle);
-            mCachedEffectiveUserRestrictions.remove(userHandle);
-            mDevicePolicyLocalUserRestrictions.remove(userHandle);
-            if (mDevicePolicyGlobalUserRestrictions.get(userHandle) != null) {
-                mDevicePolicyGlobalUserRestrictions.remove(userHandle);
+            mBaseUserRestrictions.remove(userId);
+            mAppliedUserRestrictions.remove(userId);
+            mCachedEffectiveUserRestrictions.remove(userId);
+            mDevicePolicyLocalUserRestrictions.remove(userId);
+            if (mDevicePolicyGlobalUserRestrictions.get(userId) != null) {
+                mDevicePolicyGlobalUserRestrictions.remove(userId);
                 applyUserRestrictionsForAllUsersLR();
             }
         }
@@ -3166,12 +3182,12 @@
             writeUserListLP();
         }
         // Remove user file
-        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userId + XML_SUFFIX));
         userFile.delete();
         updateUserIds();
         if (RELEASE_DELETED_USER_ID) {
             synchronized (mUsers) {
-                mRemovingUserIds.delete(userHandle);
+                mRemovingUserIds.delete(userId);
             }
         }
     }
@@ -3191,7 +3207,7 @@
     }
 
     @Override
-    public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
+    public Bundle getApplicationRestrictionsForUser(String packageName, @UserIdInt int userId) {
         if (UserHandle.getCallingUserId() != userId
                 || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
             checkSystemOrRoot("get application restrictions for other user/app " + packageName);
@@ -3204,7 +3220,7 @@
 
     @Override
     public void setApplicationRestrictions(String packageName, Bundle restrictions,
-            int userId) {
+            @UserIdInt int userId) {
         checkSystemOrRoot("set application restrictions");
         if (restrictions != null) {
             restrictions.setDefusable(true);
@@ -3238,7 +3254,8 @@
     }
 
     @GuardedBy("mAppRestrictionsLock")
-    private static Bundle readApplicationRestrictionsLAr(String packageName, int userId) {
+    private static Bundle readApplicationRestrictionsLAr(String packageName,
+            @UserIdInt int userId) {
         AtomicFile restrictionsFile =
                 new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
@@ -3332,7 +3349,7 @@
 
     @GuardedBy("mAppRestrictionsLock")
     private static void writeApplicationRestrictionsLAr(String packageName,
-            Bundle restrictions, int userId) {
+            Bundle restrictions, @UserIdInt int userId) {
         AtomicFile restrictionsFile = new AtomicFile(
                 new File(Environment.getUserSystemDirectory(userId),
                         packageToRestrictionsFileName(packageName)));
@@ -3410,17 +3427,17 @@
     }
 
     @Override
-    public int getUserSerialNumber(int userHandle) {
+    public int getUserSerialNumber(@UserIdInt int userId) {
         synchronized (mUsersLock) {
-            final UserInfo userInfo = getUserInfoLU(userHandle);
+            final UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null ? userInfo.serialNumber : -1;
         }
     }
 
     @Override
-    public boolean isUserNameSet(int userHandle) {
+    public boolean isUserNameSet(@UserIdInt int userId) {
         synchronized (mUsersLock) {
-            final UserInfo userInfo = getUserInfoLU(userHandle);
+            final UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.name != null;
         }
     }
@@ -3438,21 +3455,21 @@
     }
 
     @Override
-    public long getUserCreationTime(int userHandle) {
+    public long getUserCreationTime(@UserIdInt int userId) {
         int callingUserId = UserHandle.getCallingUserId();
         UserInfo userInfo = null;
         synchronized (mUsersLock) {
-            if (callingUserId == userHandle) {
-                userInfo = getUserInfoLU(userHandle);
+            if (callingUserId == userId) {
+                userInfo = getUserInfoLU(userId);
             } else {
-                UserInfo parent = getProfileParentLU(userHandle);
+                UserInfo parent = getProfileParentLU(userId);
                 if (parent != null && parent.id == callingUserId) {
-                    userInfo = getUserInfoLU(userHandle);
+                    userInfo = getUserInfoLU(userId);
                 }
             }
         }
         if (userInfo == null) {
-            throw new SecurityException("userHandle can only be the calling user or a managed "
+            throw new SecurityException("userId can only be the calling user or a managed "
                     + "profile associated with this user");
         }
         return userInfo.creationTime;
@@ -3485,7 +3502,7 @@
      * Called right before a user is started. This gives us a chance to prepare
      * app storage and apply any user restrictions.
      */
-    public void onBeforeStartUser(int userId) {
+    public void onBeforeStartUser(@UserIdInt int userId) {
         UserInfo userInfo = getUserInfo(userId);
         if (userInfo == null) {
             return;
@@ -3600,7 +3617,7 @@
     }
 
     @Override
-    public void setSeedAccountData(int userId, String accountName, String accountType,
+    public void setSeedAccountData(@UserIdInt int userId, String accountName, String accountType,
             PersistableBundle accountOptions, boolean persist) {
         checkManageUsersPermission("Require MANAGE_USERS permission to set user seed data");
         synchronized (mPackagesLock) {
@@ -3881,20 +3898,20 @@
      * @param userId
      * @return whether the user has been initialized yet
      */
-    boolean isUserInitialized(int userId) {
+    boolean isUserInitialized(@UserIdInt int userId) {
         return mLocalService.isUserInitialized(userId);
     }
 
     private class LocalService extends UserManagerInternal {
         @Override
-        public void setDevicePolicyUserRestrictions(int userId, @Nullable Bundle restrictions,
-                boolean isDeviceOwner, int cameraRestrictionScope) {
+        public void setDevicePolicyUserRestrictions(@UserIdInt int userId,
+                @Nullable Bundle restrictions, boolean isDeviceOwner, int cameraRestrictionScope) {
             UserManagerService.this.setDevicePolicyUserRestrictionsInner(userId, restrictions,
                 isDeviceOwner, cameraRestrictionScope);
         }
 
         @Override
-        public Bundle getBaseUserRestrictions(int userId) {
+        public Bundle getBaseUserRestrictions(@UserIdInt int userId) {
             synchronized (mRestrictionsLock) {
                 return mBaseUserRestrictions.get(userId);
             }
@@ -3902,7 +3919,7 @@
 
         @Override
         public void setBaseUserRestrictionsByDpmsForMigration(
-                int userId, Bundle baseRestrictions) {
+                @UserIdInt int userId, Bundle baseRestrictions) {
             synchronized (mRestrictionsLock) {
                 if (updateRestrictionsIfNeededLR(
                         userId, new Bundle(baseRestrictions), mBaseUserRestrictions)) {
@@ -3921,7 +3938,7 @@
         }
 
         @Override
-        public boolean getUserRestriction(int userId, String key) {
+        public boolean getUserRestriction(@UserIdInt int userId, String key) {
             return getUserRestrictions(userId).getBoolean(key);
         }
 
@@ -3947,14 +3964,14 @@
         }
 
         @Override
-        public void setUserManaged(int userId, boolean isManaged) {
+        public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
             synchronized (mUsersLock) {
                 mIsUserManaged.put(userId, isManaged);
             }
         }
 
         @Override
-        public void setUserIcon(int userId, Bitmap bitmap) {
+        public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
             long ident = Binder.clearCallingIdentity();
             try {
                 synchronized (mPackagesLock) {
@@ -4011,7 +4028,7 @@
         }
 
         @Override
-        public void onEphemeralUserStop(int userId) {
+        public void onEphemeralUserStop(@UserIdInt int userId) {
             synchronized (mUsersLock) {
                UserInfo userInfo = getUserInfoLU(userId);
                if (userInfo != null && userInfo.isEphemeral()) {
@@ -4040,26 +4057,26 @@
         }
 
         @Override
-        public boolean removeUserEvenWhenDisallowed(int userId) {
+        public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
             return removeUserUnchecked(userId);
         }
 
         @Override
-        public boolean isUserRunning(int userId) {
+        public boolean isUserRunning(@UserIdInt int userId) {
             synchronized (mUserStates) {
                 return mUserStates.get(userId, -1) >= 0;
             }
         }
 
         @Override
-        public void setUserState(int userId, int userState) {
+        public void setUserState(@UserIdInt int userId, int userState) {
             synchronized (mUserStates) {
                 mUserStates.put(userId, userState);
             }
         }
 
         @Override
-        public void removeUserState(int userId) {
+        public void removeUserState(@UserIdInt int userId) {
             synchronized (mUserStates) {
                 mUserStates.delete(userId);
             }
@@ -4071,7 +4088,7 @@
         }
 
         @Override
-        public boolean isUserUnlockingOrUnlocked(int userId) {
+        public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
@@ -4085,7 +4102,7 @@
         }
 
         @Override
-        public boolean isUserUnlocked(int userId) {
+        public boolean isUserUnlocked(@UserIdInt int userId) {
             int state;
             synchronized (mUserStates) {
                 state = mUserStates.get(userId, -1);
@@ -4098,12 +4115,12 @@
         }
 
         @Override
-        public boolean isUserInitialized(int userId) {
+        public boolean isUserInitialized(@UserIdInt int userId) {
             return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
         }
 
         @Override
-        public boolean exists(int userId) {
+        public boolean exists(@UserIdInt int userId) {
             return getUserInfoNoChecks(userId) != null;
         }
 
@@ -4147,7 +4164,7 @@
         }
 
         @Override
-        public int getProfileParentId(int userId) {
+        public int getProfileParentId(@UserIdInt int userId) {
             synchronized (mUsersLock) {
                 UserInfo profileParent = getProfileParentLU(userId);
                 if (profileParent == null) {
@@ -4165,7 +4182,7 @@
         }
 
         @Override
-        public boolean hasUserRestriction(String restrictionKey, int userId) {
+        public boolean hasUserRestriction(String restrictionKey, @UserIdInt int userId) {
             if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
                 return false;
             }
@@ -4274,7 +4291,7 @@
      * @param userId The parent user
      * @return
      */
-    boolean hasManagedProfile(int userId) {
+    boolean hasManagedProfile(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             final int userSize = mUsers.size();
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 9948a3a..b3f1867 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -277,9 +277,6 @@
         try {
             fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
             if (fd == null || !fd.getFileDescriptor().valid()) {
-                Slog.wtf(TAG,
-                        "ParcelFileDescriptor.open returned an invalid descriptor for "
-                                + packageName + ":" + snapshotProfile + ". isNull=" + (fd == null));
                 postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
             } else {
                 postSuccess(packageName, fd, callback);
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 3e655ed..793cdd2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -190,6 +190,7 @@
     static {
         STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
         STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
     }
 
     private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 22929d8..a57321e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -57,6 +57,7 @@
 import android.app.ApplicationPackageManager;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PermissionGroupInfoFlags;
 import android.content.pm.PackageManager.PermissionInfoFlags;
@@ -67,6 +68,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.Build;
@@ -791,59 +793,81 @@
 
         final CheckPermissionDelegate checkPermissionDelegate;
         synchronized (mLock) {
-            if (mCheckPermissionDelegate == null)  {
-                return checkPermissionImpl(permName, pkgName, userId);
-            }
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
+        if (checkPermissionDelegate == null)  {
+            return checkPermissionImpl(permName, pkgName, userId);
+        }
         return checkPermissionDelegate.checkPermission(permName, pkgName, userId,
-                PermissionManagerService.this::checkPermissionImpl);
+                this::checkPermissionImpl);
     }
 
-    private int checkPermissionImpl(String permName, String pkgName, int userId) {
-        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+    private int checkPermissionImpl(@NonNull String permissionName, @NonNull String packageName,
+            @UserIdInt int userId) {
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
         if (pkg == null) {
             return PackageManager.PERMISSION_DENIED;
         }
-        return checkPermissionInternal(pkg, true, permName, userId);
+        return checkPermissionInternal(pkg, true, permissionName, true, userId)
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
-    private int checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
-            @NonNull String permissionName, @UserIdInt int userId) {
+    private boolean checkPermissionInternal(@NonNull Package pkg, boolean isPackageExplicit,
+            @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps,
+            @UserIdInt int userId) {
         final int callingUid = getCallingUid();
         if (isPackageExplicit || pkg.mSharedUserId == null) {
             if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-                return PackageManager.PERMISSION_DENIED;
+                return false;
             }
         } else {
             if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-                return PackageManager.PERMISSION_DENIED;
+                return false;
             }
         }
 
         final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
         final PackageSetting ps = (PackageSetting) pkg.mExtras;
         if (ps == null) {
-            return PackageManager.PERMISSION_DENIED;
+            return false;
         }
         final PermissionsState permissionsState = ps.getPermissionsState();
 
-        if (checkSinglePermissionInternal(uid, permissionsState, permissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+        if (checkSinglePermissionInternal(uid, permissionsState, permissionName,
+                useRequestedPermissionsForLegacyApps)) {
+            return true;
         }
 
         final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
-        if (fullerPermissionName != null
-                && checkSinglePermissionInternal(uid, permissionsState, fullerPermissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+        if (fullerPermissionName != null && checkSinglePermissionInternal(uid, permissionsState,
+                fullerPermissionName, useRequestedPermissionsForLegacyApps)) {
+            return true;
         }
 
-        return PackageManager.PERMISSION_DENIED;
+        return false;
     }
 
     private boolean checkSinglePermissionInternal(int uid,
-            @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
-        if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
+            @NonNull PermissionsState permissionsState, @NonNull String permissionName,
+            boolean useRequestedPermissionsForLegacyApps) {
+        boolean hasPermission = permissionsState.hasPermission(permissionName,
+                UserHandle.getUserId(uid));
+
+        if (!hasPermission && useRequestedPermissionsForLegacyApps
+                && mSettings.isPermissionRuntime(permissionName)) {
+            final String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            final int packageNamesSize = packageNames != null ? packageNames.length : 0;
+            for (int i = 0; i < packageNamesSize; i++) {
+                final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageNames[i]);
+                if (pkg != null && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                        && pkg.requestedPermissions.contains(permissionName)) {
+                    hasPermission = true;
+                    break;
+                }
+            }
+        }
+
+        if (!hasPermission) {
             return false;
         }
 
@@ -873,12 +897,13 @@
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
         return checkPermissionDelegate.checkUidPermission(permName, uid,
-                PermissionManagerService.this::checkUidPermissionImpl);
+                this::checkUidPermissionImpl);
     }
 
-    private int checkUidPermissionImpl(String permName, int uid) {
+    private int checkUidPermissionImpl(@NonNull String permissionName, int uid) {
         final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
-        return checkUidPermissionInternal(pkg, uid, permName);
+        return checkUidPermissionInternal(uid, pkg, permissionName, true)
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED;
     }
 
     /**
@@ -888,24 +913,25 @@
      *
      * @see SystemConfig#getSystemPermissions()
      */
-    private int checkUidPermissionInternal(@Nullable Package pkg, int uid,
-            @NonNull String permissionName) {
+    private boolean checkUidPermissionInternal(int uid, @Nullable Package pkg,
+            @NonNull String permissionName, boolean useRequestedPermissionsForLegacyApps) {
         if (pkg != null) {
             final int userId = UserHandle.getUserId(uid);
-            return checkPermissionInternal(pkg, false, permissionName, userId);
+            return checkPermissionInternal(pkg, false, permissionName,
+                    useRequestedPermissionsForLegacyApps, userId);
         }
 
         if (checkSingleUidPermissionInternal(uid, permissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+            return true;
         }
 
         final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
         if (fullerPermissionName != null
                 && checkSingleUidPermissionInternal(uid, fullerPermissionName)) {
-            return PackageManager.PERMISSION_GRANTED;
+            return true;
         }
 
-        return PackageManager.PERMISSION_DENIED;
+        return false;
     }
 
     private boolean checkSingleUidPermissionInternal(int uid, @NonNull String permissionName) {
@@ -915,6 +941,17 @@
         }
     }
 
+    private int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+        boolean granted = isUidPermissionGranted(uid, permissionName);
+        // TODO: Foreground permissions.
+        return granted ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED;
+    }
+
+    private boolean isUidPermissionGranted(int uid, @NonNull String permissionName) {
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(uid);
+        return checkUidPermissionInternal(uid, pkg, permissionName, false);
+    }
+
     @Override
     public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
         mContext.enforceCallingOrSelfPermission(
@@ -2332,10 +2369,11 @@
                         // or has updated its target SDK and AR is no longer implicit to it.
                         // This is a compatibility workaround for apps when AR permission was
                         // split in Q.
-                        int numSplitPerms = PermissionManager.SPLIT_PERMISSIONS.size();
+                        final List<SplitPermissionInfoParcelable> permissionList =
+                                getSplitPermissions();
+                        int numSplitPerms = permissionList.size();
                         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-                            PermissionManager.SplitPermissionInfo sp =
-                                    PermissionManager.SPLIT_PERMISSIONS.get(splitPermNum);
+                            SplitPermissionInfoParcelable sp = permissionList.get(splitPermNum);
                             String splitPermName = sp.getSplitPermission();
                             if (sp.getNewPermissions().contains(permName)
                                     && origPermissions.hasInstallPermission(splitPermName)) {
@@ -2904,10 +2942,10 @@
         String pkgName = pkg.packageName;
         ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
 
-        int numSplitPerms = PermissionManager.SPLIT_PERMISSIONS.size();
+        final List<SplitPermissionInfoParcelable> permissionList = getSplitPermissions();
+        int numSplitPerms = permissionList.size();
         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-            PermissionManager.SplitPermissionInfo spi =
-                    PermissionManager.SPLIT_PERMISSIONS.get(splitPermNum);
+            SplitPermissionInfoParcelable spi = permissionList.get(splitPermNum);
 
             List<String> newPerms = spi.getNewPermissions();
             int numNewPerms = newPerms.size();
@@ -2962,8 +3000,6 @@
                                 Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
                                         + " for " + pkgName + " as split permission is also new");
                             }
-
-                            break;
                         } else {
                             // Inherit from new install or existing runtime permissions
                             inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms,
@@ -2977,6 +3013,12 @@
         return updatedUserIds;
     }
 
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        return PermissionManager.splitPermissionInfoListToParcelableList(
+                SystemConfig.getInstance().getSplitPermissions());
+    }
+
     private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
         boolean allowed = false;
         final int NP = PackageParser.NEW_PERMISSIONS.length;
@@ -3728,7 +3770,7 @@
         // Make sure all dynamic permissions have been assigned to a package,
         // and make sure there are no dangling permissions.
         boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName,
-                changingPkg);
+                changingPkg, callback);
 
         if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) {
             // Permission ownership has changed. This e.g. changes which packages can get signature
@@ -3781,7 +3823,8 @@
      * @return {@code true} if a permission source package might have changed
      */
     private boolean updatePermissionSourcePackage(@Nullable String packageName,
-            @Nullable PackageParser.Package pkg) {
+            @Nullable PackageParser.Package pkg,
+            final @Nullable PermissionCallback callback) {
         boolean changed = false;
 
         Set<BasePermission> needsUpdate = null;
@@ -3797,6 +3840,45 @@
                         && (pkg == null || !hasPermission(pkg, bp.getName()))) {
                         Slog.i(TAG, "Removing permission " + bp.getName()
                                 + " that used to be declared by " + bp.getSourcePackageName());
+                        if (bp.isRuntime()) {
+                            final int[] userIds = mUserManagerInt.getUserIds();
+                            final int numUserIds = userIds.length;
+                            for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
+                                final int userId = userIds[userIdNum];
+
+                                mPackageManagerInt.forEachPackage((Package p) -> {
+                                    final String pName = p.packageName;
+                                    final ApplicationInfo appInfo =
+                                            mPackageManagerInt.getApplicationInfo(pName, 0,
+                                                    Process.SYSTEM_UID, UserHandle.USER_SYSTEM);
+                                    if (appInfo != null
+                                            && appInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                                        return;
+                                    }
+
+                                    final String permissionName = bp.getName();
+                                    if (checkPermissionImpl(permissionName, pName, userId)
+                                            == PackageManager.PERMISSION_GRANTED) {
+                                        try {
+                                            revokeRuntimePermissionInternal(
+                                                    permissionName,
+                                                    pName,
+                                                    false,
+                                                    Process.SYSTEM_UID,
+                                                    userId,
+                                                    callback);
+                                        } catch (IllegalArgumentException e) {
+                                            Slog.e(TAG,
+                                                    "Failed to revoke "
+                                                            + permissionName
+                                                            + " from "
+                                                            + pName,
+                                                    e);
+                                        }
+                                    }
+                                });
+                            }
+                        }
                         changed = true;
                         it.remove();
                     }
@@ -4383,6 +4465,12 @@
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
             }
         }
+
+        @Override
+        public int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName) {
+            return PermissionManagerService.this.computeRuntimePermissionAppOpMode(uid,
+                    permissionName);
+        }
     }
 
     private static final class OnPermissionChangeListeners extends Handler {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 04ec5ba..8f22f92 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -445,4 +445,13 @@
 
     /** Called when a new user has been created. */
     public abstract void onNewUserCreated(@UserIdInt int userId);
+
+    /**
+     * Compute an app op mode based on its runtime permission state.
+     *
+     * @param uid the uid for the app op
+     * @param permissionName the permission name for the app op
+     * @return the computed mode
+     */
+    public abstract int computeRuntimePermissionAppOpMode(int uid, @NonNull String permissionName);
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
index 9208032..3d8cf2d 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionSettings.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -18,13 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.content.pm.PackageParser;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.DumpState;
@@ -37,7 +35,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.Collection;
-import java.util.Set;
 
 /**
  * Permissions and other related data. This class is not meant for
@@ -249,6 +246,18 @@
         }
     }
 
+    /**
+     * Check whether a permission is runtime.
+     *
+     * @see BasePermission#isRuntime()
+     */
+    public boolean isPermissionRuntime(@NonNull String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isRuntime());
+        }
+    }
+
     public boolean isPermissionInstant(String permName) {
         synchronized (mLock) {
             final BasePermission bp = mPermissions.get(permName);
diff --git a/services/core/java/com/android/server/policy/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/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/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
index cae09ea3..3f9cc83 100644
--- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
+++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java
@@ -23,17 +23,12 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
@@ -56,6 +51,8 @@
      * {@code userIds}. Updates said {@code packageRollbackInfo} with the inodes of the CE user data
      * snapshot folders.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void snapshotAppData(
             int snapshotId, PackageRollbackInfo packageRollbackInfo, int[] userIds) {
         for (int user : userIds) {
@@ -92,6 +89,8 @@
      *         to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
      *         userId} to the list of pending backups or restores.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int userId, int appId, String seInfo) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -135,6 +134,8 @@
      * Deletes an app data snapshot with a given {@code rollbackId} for a specified package
      * {@code packageName} for a given {@code user}.
      */
+    @GuardedBy("rollback.getLock")
+    // TODO(b/136241838): Move into Rollback and synchronize there.
     public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
             int user) {
         int storageFlags = Installer.FLAG_STORAGE_DE;
@@ -156,141 +157,68 @@
     }
 
     /**
-     * Computes the list of pending backups for {@code userId} given lists of rollbacks.
-     * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
-     * with their corresponding {@code PackageRollbackInfo}.
+     * Commits the pending backups and restores for a given {@code userId} and {@code rollback}. If
+     * the rollback has a pending backup, it is updated with a mapping from {@code userId} to inode
+     * of the CE user data snapshot.
      *
-     * @return the list of rollbacks that have pending backups. Note that some of the
-     *         backups won't be performed, because they might be counteracted by pending restores.
+     * @return true if any backups or restores were found for the userId
      */
-    private static List<Rollback> computePendingBackups(int userId,
-            Map<String, PackageRollbackInfo> pendingBackupPackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingBackups = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final IntArray pendingBackupUsers = info.getPendingBackups();
-                if (pendingBackupUsers != null) {
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        pendingBackupPackages.put(info.getPackageName(), info);
-                        if (rollbacksWithPendingBackups.indexOf(rollback) == -1) {
-                            rollbacksWithPendingBackups.add(rollback);
-                        }
-                    }
+    @GuardedBy("rollback.getLock")
+    boolean commitPendingBackupAndRestoreForUser(int userId, Rollback rollback) {
+        boolean foundBackupOrRestore = false;
+        for (PackageRollbackInfo info : rollback.info.getPackages()) {
+            boolean hasPendingBackup = false;
+            boolean hasPendingRestore = false;
+            final IntArray pendingBackupUsers = info.getPendingBackups();
+            if (pendingBackupUsers != null) {
+                if (pendingBackupUsers.indexOf(userId) != -1) {
+                    hasPendingBackup = true;
+                    foundBackupOrRestore = true;
                 }
             }
-        }
-        return rollbacksWithPendingBackups;
-    }
 
-    /**
-     * Computes the list of pending restores for {@code userId} given lists of rollbacks.
-     * Packages pending restore are added to {@code pendingRestores} along with their corresponding
-     * {@code PackageRollbackInfo}.
-     *
-     * @return the list of rollbacks that have pending restores. Note that some of the
-     *         restores won't be performed, because they might be counteracted by pending backups.
-     */
-    private static List<Rollback> computePendingRestores(int userId,
-            Map<String, PackageRollbackInfo> pendingRestorePackages,
-            List<Rollback> rollbacks) {
-        List<Rollback> rollbacksWithPendingRestores = new ArrayList<>();
-
-        for (Rollback rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                final RestoreInfo ri = info.getRestoreInfo(userId);
-                if (ri != null) {
-                    pendingRestorePackages.put(info.getPackageName(), info);
-                    if (rollbacksWithPendingRestores.indexOf(rollback) == -1) {
-                        rollbacksWithPendingRestores.add(rollback);
-                    }
-                }
+            RestoreInfo ri = info.getRestoreInfo(userId);
+            if (ri != null) {
+                hasPendingRestore = true;
+                foundBackupOrRestore = true;
             }
-        }
 
-        return rollbacksWithPendingRestores;
-    }
-
-    /**
-     * Commits the list of pending backups and restores for a given {@code userId}. For rollbacks
-     * with pending backups, updates the {@code Rollback} instance with a mapping from
-     * {@code userId} to inode of the CE user data snapshot.
-     *
-     * @return the set of rollbacks with changes that should be stored on disk.
-     */
-    public Set<Rollback> commitPendingBackupAndRestoreForUser(int userId,
-            List<Rollback> rollbacks) {
-
-        final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
-        final List<Rollback> pendingBackups = computePendingBackups(userId,
-                pendingBackupPackages, rollbacks);
-
-        final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
-        final List<Rollback> pendingRestores = computePendingRestores(userId,
-                pendingRestorePackages, rollbacks);
-
-        // First remove unnecessary backups, i.e. when user did not unlock their phone between the
-        // request to backup data and the request to restore it.
-        Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
-                pendingBackupPackages.entrySet().iterator();
-        while (iter.hasNext()) {
-            PackageRollbackInfo backupPackage = iter.next().getValue();
-            PackageRollbackInfo restorePackage =
-                    pendingRestorePackages.get(backupPackage.getPackageName());
-            if (restorePackage != null) {
-                backupPackage.removePendingBackup(userId);
-                backupPackage.removePendingRestoreInfo(userId);
-                iter.remove();
-                pendingRestorePackages.remove(backupPackage.getPackageName());
+            if (hasPendingBackup && hasPendingRestore) {
+                // Remove unnecessary backup, i.e. when user did not unlock their phone between the
+                // request to backup data and the request to restore it.
+                info.removePendingBackup(userId);
+                info.removePendingRestoreInfo(userId);
+                continue;
             }
-        }
 
-        if (!pendingBackupPackages.isEmpty()) {
-            for (Rollback rollback : pendingBackups) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final IntArray pendingBackupUsers = info.getPendingBackups();
-                    final int idx = pendingBackupUsers.indexOf(userId);
-                    if (idx != -1) {
-                        try {
-                            long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
-                                    userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.putCeSnapshotInode(userId, ceSnapshotInode);
-                            pendingBackupUsers.remove(idx);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG,
-                                    "Unable to create app data snapshot for: "
+            if (hasPendingBackup) {
+                int idx = pendingBackupUsers.indexOf(userId);
+                try {
+                    long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
+                            userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.putCeSnapshotInode(userId, ceSnapshotInode);
+                    pendingBackupUsers.remove(idx);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG,
+                            "Unable to create app data snapshot for: "
                                     + info.getPackageName() + ", userId: " + userId, ie);
-                        }
-                    }
+                }
+            }
+
+            if (hasPendingRestore) {
+                try {
+                    mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
+                            ri.seInfo, userId, rollback.info.getRollbackId(),
+                            Installer.FLAG_STORAGE_CE);
+                    info.removeRestoreInfo(ri);
+                } catch (InstallerException ie) {
+                    Slog.e(TAG, "Unable to restore app data snapshot for: "
+                            + info.getPackageName(), ie);
                 }
             }
         }
-
-        if (!pendingRestorePackages.isEmpty()) {
-            for (Rollback rollback : pendingRestores) {
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    final RestoreInfo ri = info.getRestoreInfo(userId);
-                    if (ri != null) {
-                        try {
-                            mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
-                                    ri.seInfo, userId, rollback.info.getRollbackId(),
-                                    Installer.FLAG_STORAGE_CE);
-                            info.removeRestoreInfo(ri);
-                        } catch (InstallerException ie) {
-                            Slog.e(TAG, "Unable to restore app data snapshot for: "
-                                    + info.getPackageName(), ie);
-                        }
-                    }
-                }
-            }
-        }
-
-        final Set<Rollback> changed = new HashSet<>(pendingBackups);
-        changed.addAll(pendingRestores);
-        return changed;
+        return foundBackupOrRestore;
     }
 
     /**
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 0d5746b..2dc4951 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -18,19 +18,27 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.text.ParseException;
 import java.time.Instant;
 import java.util.ArrayList;
+import java.util.List;
 
 
 /**
- * Information about a rollback available for a set of atomically installed
- * packages.
+ * Information about a rollback available for a set of atomically installed packages.
+ *
+ * <p>When accessing the state of a Rollback object, the caller is responsible for synchronization.
+ * The lock object provided by {@link #getLock} should be acquired when accessing any of the mutable
+ * state of a Rollback, including from the {@link RollbackInfo} and any of the
+ * {@link PackageRollbackInfo} objects held within.
  */
 class Rollback {
     @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
@@ -58,14 +66,24 @@
     static final int ROLLBACK_STATE_COMMITTED = 3;
 
     /**
-     * The rollback info for this rollback.
+     * The session ID for the staged session if this rollback data represents a staged session,
+     * {@code -1} otherwise.
      */
+    private final int mStagedSessionId;
+
+    /**
+     * The rollback info for this rollback.
+     *
+     * <p>Any access to this field that touches any mutable state should be synchronized on
+     * {@link #getLock}.
+     */
+    @GuardedBy("getLock")
     public final RollbackInfo info;
 
     /**
      * The directory where the rollback data is stored.
      */
-    public final File backupDir;
+    private final File mBackupDir;
 
     /**
      * The time when the upgrade occurred, for purposes of expiring
@@ -74,32 +92,36 @@
      * The timestamp is not applicable for all rollback states, but we make
      * sure to keep it non-null to avoid potential errors there.
      */
-    public @NonNull Instant timestamp;
-
-    /**
-     * The session ID for the staged session if this rollback data represents a staged session,
-     * {@code -1} otherwise.
-     */
-    public final int stagedSessionId;
+    @GuardedBy("mLock")
+    private @NonNull Instant mTimestamp;
 
     /**
      * The current state of the rollback.
      * ENABLING, AVAILABLE, or COMMITTED.
      */
-    public @RollbackState int state;
+    @GuardedBy("mLock")
+    private @RollbackState int mState;
 
     /**
      * The id of the post-reboot apk session for a staged install, if any.
      */
-    public int apkSessionId = -1;
+    @GuardedBy("mLock")
+    private int mApkSessionId = -1;
 
     /**
      * True if we are expecting the package manager to call restoreUserData
      * for this rollback because it has just been committed but the rollback
      * has not yet been fully applied.
      */
-    // NOTE: All accesses to this field are from the RollbackManager handler thread.
-    public boolean restoreUserDataInProgress = false;
+    @GuardedBy("mLock")
+    private boolean mRestoreUserDataInProgress = false;
+
+    /**
+     * Lock object to guard all access to Rollback state.
+     *
+     * @see #getLock
+     */
+    private final Object mLock = new Object();
 
     /**
      * Constructs a new, empty Rollback instance.
@@ -114,10 +136,10 @@
                 /* isStaged */ stagedSessionId != -1,
                 /* causePackages */ new ArrayList<>(),
                 /* committedSessionId */ -1);
-        this.backupDir = backupDir;
-        this.stagedSessionId = stagedSessionId;
-        this.state = ROLLBACK_STATE_ENABLING;
-        this.timestamp = Instant.now();
+        mBackupDir = backupDir;
+        mStagedSessionId = stagedSessionId;
+        mState = ROLLBACK_STATE_ENABLING;
+        mTimestamp = Instant.now();
     }
 
     /**
@@ -126,21 +148,195 @@
     Rollback(RollbackInfo info, File backupDir, Instant timestamp, int stagedSessionId,
             @RollbackState int state, int apkSessionId, boolean restoreUserDataInProgress) {
         this.info = info;
-        this.backupDir = backupDir;
-        this.timestamp = timestamp;
-        this.stagedSessionId = stagedSessionId;
-        this.state = state;
-        this.apkSessionId = apkSessionId;
-        this.restoreUserDataInProgress = restoreUserDataInProgress;
+        mBackupDir = backupDir;
+        mTimestamp = timestamp;
+        mStagedSessionId = stagedSessionId;
+        mState = state;
+        mApkSessionId = apkSessionId;
+        mRestoreUserDataInProgress = restoreUserDataInProgress;
+    }
+
+    /**
+     * Returns a lock object that should be acquired before accessing any Rollback state from
+     * {@link RollbackManagerServiceImpl}.
+     *
+     * <p>Note that while holding this lock, the lock for {@link RollbackManagerServiceImpl} should
+     * not be acquired (but it is ok to acquire this lock while already holding the lock for that
+     * class).
+     */
+    // TODO(b/136241838): Move rollback functionality into this class and synchronize on the lock
+    // internally. Remove this method once this has been done for all cases.
+    Object getLock() {
+        return mLock;
     }
 
     /**
      * Whether the rollback is for rollback of a staged install.
      */
-    public boolean isStaged() {
+    @GuardedBy("getLock")
+    boolean isStaged() {
         return info.isStaged();
     }
 
+    /**
+     * Returns the directory in which rollback data should be stored.
+     */
+    File getBackupDir() {
+        return mBackupDir;
+    }
+
+    /**
+     * Returns the time when the upgrade occurred, for purposes of expiring rollback data.
+     */
+    @GuardedBy("getLock")
+    Instant getTimestamp() {
+        return mTimestamp;
+    }
+
+    /**
+     * Sets the time at which upgrade occurred.
+     */
+    @GuardedBy("getLock")
+    void setTimestamp(Instant timestamp) {
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * Returns the session ID for the staged session if this rollback data represents a staged
+     * session, or {@code -1} otherwise.
+     */
+    int getStagedSessionId() {
+        return mStagedSessionId;
+    }
+
+    /**
+     * Returns true if the rollback is in the ENABLING state.
+     */
+    @GuardedBy("getLock")
+    boolean isEnabling() {
+        return mState == ROLLBACK_STATE_ENABLING;
+    }
+
+    /**
+     * Returns true if the rollback is in the AVAILABLE state.
+     */
+    @GuardedBy("getLock")
+    boolean isAvailable() {
+        return mState == ROLLBACK_STATE_AVAILABLE;
+    }
+
+    /**
+     * Returns true if the rollback is in the COMMITTED state.
+     */
+    @GuardedBy("getLock")
+    boolean isCommitted() {
+        return mState == ROLLBACK_STATE_COMMITTED;
+    }
+
+    /**
+     * Sets the state of the rollback to AVAILABLE.
+     */
+    @GuardedBy("getLock")
+    void setAvailable() {
+        mState = ROLLBACK_STATE_AVAILABLE;
+    }
+
+    /**
+     * Sets the state of the rollback to COMMITTED.
+     */
+    @GuardedBy("getLock")
+    void setCommitted() {
+        mState = ROLLBACK_STATE_COMMITTED;
+    }
+
+    /**
+     * Returns the id of the post-reboot apk session for a staged install, if any.
+     */
+    @GuardedBy("getLock")
+    int getApkSessionId() {
+        return mApkSessionId;
+    }
+
+    /**
+     * Sets the id of the post-reboot apk session for a staged install.
+     */
+    @GuardedBy("getLock")
+    void setApkSessionId(int apkSessionId) {
+        mApkSessionId = apkSessionId;
+    }
+
+    /**
+     * Returns true if we are expecting the package manager to call restoreUserData for this
+     * rollback because it has just been committed but the rollback has not yet been fully applied.
+     */
+    @GuardedBy("getLock")
+    boolean isRestoreUserDataInProgress() {
+        return mRestoreUserDataInProgress;
+    }
+
+    /**
+     * Sets whether we are expecting the package manager to call restoreUserData for this
+     * rollback because it has just been committed but the rollback has not yet been fully applied.
+     */
+    @GuardedBy("getLock")
+    void setRestoreUserDataInProgress(boolean restoreUserDataInProgress) {
+        mRestoreUserDataInProgress = restoreUserDataInProgress;
+    }
+
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackage(String packageName) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this rollback includes the package with the provided {@code packageName}
+     * with a <i>version rolled back from</i> that is not {@code versionCode}.
+     */
+    @GuardedBy("getLock")
+    boolean includesPackageWithDifferentVersion(String packageName, long versionCode) {
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.getPackageName().equals(packageName)
+                    && info.getVersionRolledBackFrom().getLongVersionCode() != versionCode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns a list containing the names of all the packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            result.add(info.getPackageName());
+        }
+        return result;
+    }
+
+    /**
+     * Returns a list containing the names of all the apex packages included in this rollback.
+     */
+    @GuardedBy("getLock")
+    List<String> getApexPackageNames() {
+        List<String> result = new ArrayList<>();
+        for (PackageRollbackInfo info : info.getPackages()) {
+            if (info.isApex()) {
+                result.add(info.getPackageName());
+            }
+        }
+        return result;
+    }
+
     static String rollbackStateToString(@RollbackState int state) {
         switch (state) {
             case Rollback.ROLLBACK_STATE_ENABLING: return "enabling";
@@ -160,7 +356,8 @@
         throw new ParseException("Invalid rollback state: " + state, 0);
     }
 
-    public String getStateAsString() {
-        return rollbackStateToString(state);
+    @GuardedBy("getLock")
+    String getStateAsString() {
+        return rollbackStateToString(mState);
     }
 }
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index ddbd9c9..e8e448a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -118,6 +118,10 @@
     @GuardedBy("mLock")
     private final List<Rollback> mRollbacks;
 
+    // Apk sessions from a staged session with no matching rollback.
+    @GuardedBy("mLock")
+    private final IntArray mOrphanedApkSessionIds = new IntArray();
+
     private final RollbackStore mRollbackStore;
 
     private final Context mContext;
@@ -278,8 +282,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isAvailable()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -294,8 +300,10 @@
             List<RollbackInfo> rollbacks = new ArrayList<>();
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
-                    rollbacks.add(rollback.info);
+                synchronized (rollback.getLock()) {
+                    if (rollback.isCommitted()) {
+                        rollbacks.add(rollback.info);
+                    }
                 }
             }
             return new ParceledListSlice<>(rollbacks);
@@ -328,8 +336,11 @@
                     Iterator<Rollback> iter = mRollbacks.iterator();
                     while (iter.hasNext()) {
                         Rollback rollback = iter.next();
-                        rollback.timestamp = rollback.timestamp.plusMillis(timeDifference);
-                        saveRollback(rollback);
+                        synchronized (rollback.getLock()) {
+                            rollback.setTimestamp(
+                                    rollback.getTimestamp().plusMillis(timeDifference));
+                            saveRollback(rollback);
+                        }
                     }
                 }
             }
@@ -354,86 +365,94 @@
         Slog.i(TAG, "Initiating rollback");
 
         Rollback rollback = getRollbackForId(rollbackId);
-        if (rollback == null || rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
+        if (rollback == null) {
             sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
                     "Rollback unavailable");
             return;
         }
-
-        // Get a context for the caller to use to install the downgraded
-        // version of the package.
-        final Context context;
-        try {
-            context = mContext.createPackageContext(callerPackageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "Invalid callerPackageName");
-            return;
-        }
-
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInstaller packageInstaller = pm.getPackageInstaller();
-            PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
-                    PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-            parentParams.setRequestDowngrade(true);
-            parentParams.setMultiPackage();
-            if (rollback.isStaged()) {
-                parentParams.setStaged();
+        synchronized (rollback.getLock()) {
+            if (!rollback.isAvailable()) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_ROLLBACK_UNAVAILABLE,
+                        "Rollback unavailable");
+                return;
             }
 
-            int parentSessionId = packageInstaller.createSession(parentParams);
-            PackageInstaller.Session parentSession = packageInstaller.openSession(parentSessionId);
+            // Get a context for the caller to use to install the downgraded
+            // version of the package.
+            final Context context;
+            try {
+                context = mContext.createPackageContext(callerPackageName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "Invalid callerPackageName");
+                return;
+            }
 
-            for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+            PackageManager pm = context.getPackageManager();
+            try {
+                PackageInstaller packageInstaller = pm.getPackageInstaller();
+                PackageInstaller.SessionParams parentParams = new PackageInstaller.SessionParams(
                         PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-                // TODO: We can't get the installerPackageName for apex
-                // (b/123920130). Is it okay to ignore the installer package
-                // for apex?
-                if (!info.isApex()) {
-                    String installerPackageName = pm.getInstallerPackageName(info.getPackageName());
-                    if (installerPackageName != null) {
-                        params.setInstallerPackageName(installerPackageName);
-                    }
-                }
-                params.setRequestDowngrade(true);
-                params.setRequiredInstalledVersionCode(
-                        info.getVersionRolledBackFrom().getLongVersionCode());
+                parentParams.setRequestDowngrade(true);
+                parentParams.setMultiPackage();
                 if (rollback.isStaged()) {
-                    params.setStaged();
-                }
-                if (info.isApex()) {
-                    params.setInstallAsApex();
-                }
-                int sessionId = packageInstaller.createSession(params);
-                PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-                File[] packageCodePaths = RollbackStore.getPackageCodePaths(
-                        rollback, info.getPackageName());
-                if (packageCodePaths == null) {
-                    sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                            "Backup copy of package inaccessible");
-                    return;
+                    parentParams.setStaged();
                 }
 
-                for (File packageCodePath : packageCodePaths) {
-                    try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
-                                ParcelFileDescriptor.MODE_READ_ONLY)) {
-                        final long token = Binder.clearCallingIdentity();
-                        try {
-                            session.write(packageCodePath.getName(), 0, packageCodePath.length(),
-                                    fd);
-                        } finally {
-                            Binder.restoreCallingIdentity(token);
+                int parentSessionId = packageInstaller.createSession(parentParams);
+                PackageInstaller.Session parentSession = packageInstaller.openSession(
+                        parentSessionId);
+
+                for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                    PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                            PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+                    // TODO: We can't get the installerPackageName for apex
+                    // (b/123920130). Is it okay to ignore the installer package
+                    // for apex?
+                    if (!info.isApex()) {
+                        String installerPackageName =
+                                pm.getInstallerPackageName(info.getPackageName());
+                        if (installerPackageName != null) {
+                            params.setInstallerPackageName(installerPackageName);
                         }
                     }
-                }
-                parentSession.addChildSessionId(sessionId);
-            }
+                    params.setRequestDowngrade(true);
+                    params.setRequiredInstalledVersionCode(
+                            info.getVersionRolledBackFrom().getLongVersionCode());
+                    if (rollback.isStaged()) {
+                        params.setStaged();
+                    }
+                    if (info.isApex()) {
+                        params.setInstallAsApex();
+                    }
+                    int sessionId = packageInstaller.createSession(params);
+                    PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+                    File[] packageCodePaths = RollbackStore.getPackageCodePaths(
+                            rollback, info.getPackageName());
+                    if (packageCodePaths == null) {
+                        sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                                "Backup copy of package inaccessible");
+                        return;
+                    }
 
-            final LocalIntentReceiver receiver = new LocalIntentReceiver(
-                    (Intent result) -> {
-                        getHandler().post(() -> {
+                    for (File packageCodePath : packageCodePaths) {
+                        try (ParcelFileDescriptor fd = ParcelFileDescriptor.open(packageCodePath,
+                                ParcelFileDescriptor.MODE_READ_ONLY)) {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                session.write(packageCodePath.getName(), 0,
+                                        packageCodePath.length(),
+                                        fd);
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    }
+                    parentSession.addChildSessionId(sessionId);
+                }
+
+                final LocalIntentReceiver receiver = new LocalIntentReceiver(
+                        (Intent result) -> getHandler().post(() -> {
 
                             int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                                     PackageInstaller.STATUS_FAILURE);
@@ -446,55 +465,55 @@
                                 // TODO: Should we just kill this rollback if
                                 // commit failed? Why would we expect commit
                                 // not to fail again?
-                                synchronized (mLock) {
-                                    // TODO: Could this cause a rollback to be
-                                    // resurrected if it should otherwise have
-                                    // expired by now?
-                                    rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
-                                    rollback.restoreUserDataInProgress = false;
+                                // TODO: Could this cause a rollback to be
+                                // resurrected if it should otherwise have
+                                // expired by now?
+                                synchronized (rollback.getLock()) {
+                                    rollback.setAvailable();
+                                    rollback.setRestoreUserDataInProgress(false);
                                 }
-                                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE_INSTALL,
+                                sendFailure(statusReceiver,
+                                        RollbackManager.STATUS_FAILURE_INSTALL,
                                         "Rollback downgrade install failed: "
-                                        + result.getStringExtra(
+                                                + result.getStringExtra(
                                                 PackageInstaller.EXTRA_STATUS_MESSAGE));
                                 return;
                             }
 
-                            synchronized (mLock) {
+                            synchronized (rollback.getLock()) {
                                 if (!rollback.isStaged()) {
                                     // All calls to restoreUserData should have
                                     // completed by now for a non-staged install.
-                                    rollback.restoreUserDataInProgress = false;
+                                    rollback.setRestoreUserDataInProgress(false);
                                 }
 
                                 rollback.info.setCommittedSessionId(parentSessionId);
                                 rollback.info.getCausePackages().addAll(causePackages);
+                                RollbackStore.deletePackageCodePaths(rollback);
+                                saveRollback(rollback);
                             }
-                            mRollbackStore.deletePackageCodePaths(rollback);
-                            saveRollback(rollback);
 
                             sendSuccess(statusReceiver);
 
                             Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
 
                             for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
-                                mContext.sendBroadcastAsUser(broadcast, userInfo.getUserHandle(),
+                                mContext.sendBroadcastAsUser(broadcast,
+                                        userInfo.getUserHandle(),
                                         Manifest.permission.MANAGE_ROLLBACKS);
                             }
-                        });
-                    }
-            );
+                        })
+                );
 
-            synchronized (mLock) {
-                rollback.state = Rollback.ROLLBACK_STATE_COMMITTED;
-                rollback.restoreUserDataInProgress = true;
+                rollback.setCommitted();
+                rollback.setRestoreUserDataInProgress(true);
+                parentSession.commit(receiver.getIntentSender());
+            } catch (IOException e) {
+                Slog.e(TAG, "Rollback failed", e);
+                sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
+                        "IOException: " + e.toString());
+                return;
             }
-            parentSession.commit(receiver.getIntentSender());
-        } catch (IOException e) {
-            Slog.e(TAG, "Rollback failed", e);
-            sendFailure(statusReceiver, RollbackManager.STATUS_FAILURE,
-                    "IOException: " + e.toString());
-            return;
         }
     }
 
@@ -530,19 +549,17 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (rollback.getLock()) {
+                    if (rollback.includesPackage(packageName)) {
                         iter.remove();
                         deleteRollback(rollback);
-                        break;
                     }
                 }
             }
             for (NewRollback newRollback : mNewRollbacks) {
-                for (PackageRollbackInfo info : newRollback.rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
+                synchronized (newRollback.rollback.getLock()) {
+                    if (newRollback.rollback.includesPackage(packageName)) {
                         newRollback.isCancelled = true;
-                        break;
                     }
                 }
             }
@@ -564,19 +581,34 @@
     }
 
     void onUnlockUser(int userId) {
+        // In order to ensure that no package begins running while a backup or restore is taking
+        // place, onUnlockUser must remain blocked until all pending backups and restores have
+        // completed.
+        CountDownLatch latch = new CountDownLatch(1);
         getHandler().post(() -> {
             final List<Rollback> rollbacks;
             synchronized (mLock) {
                 rollbacks = new ArrayList<>(mRollbacks);
             }
 
-            final Set<Rollback> changed =
-                    mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, rollbacks);
-
-            for (Rollback rollback : changed) {
-                saveRollback(rollback);
+            for (int i = 0; i < rollbacks.size(); i++) {
+                Rollback rollback = rollbacks.get(i);
+                synchronized (rollback.getLock()) {
+                    if (mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(
+                            userId, rollback)) {
+                        saveRollback(rollback);
+                    }
+                }
             }
+
+            latch.countDown();
         });
+
+        try {
+            latch.await();
+        } catch (InterruptedException ie) {
+            throw new IllegalStateException("RollbackManagerHandlerThread interrupted");
+        }
     }
 
     private void updateRollbackLifetimeDurationInMillis() {
@@ -602,17 +634,15 @@
             Set<String> apexPackageNames = new HashSet<>();
             synchronized (mLock) {
                 for (Rollback rollback : mRollbacks) {
-                    if (rollback.isStaged()) {
-                        if (rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
-                            enabling.add(rollback);
-                        } else if (rollback.restoreUserDataInProgress) {
-                            restoreInProgress.add(rollback);
-                        }
-
-                        for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                            if (info.isApex()) {
-                                apexPackageNames.add(info.getPackageName());
+                    synchronized (rollback.getLock()) {
+                        if (rollback.isStaged()) {
+                            if (rollback.isEnabling()) {
+                                enabling.add(rollback);
+                            } else if (rollback.isRestoreUserDataInProgress()) {
+                                restoreInProgress.add(rollback);
                             }
+
+                            apexPackageNames.addAll(rollback.getApexPackageNames());
                         }
                     }
                 }
@@ -620,30 +650,32 @@
 
             for (Rollback rollback : enabling) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session = installer.getSessionInfo(
-                        rollback.stagedSessionId);
-                if (session == null || session.isStagedSessionFailed()) {
-                    // TODO: Do we need to remove this from
-                    // mRollbacks, or is it okay to leave as
-                    // unavailable until the next reboot when it will go
-                    // away on its own?
-                    deleteRollback(rollback);
-                } else if (session.isStagedSessionApplied()) {
-                    makeRollbackAvailable(rollback);
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    if (session == null || session.isStagedSessionFailed()) {
+                        // TODO: Do we need to remove this from
+                        // mRollbacks, or is it okay to leave as
+                        // unavailable until the next reboot when it will go
+                        // away on its own?
+                        deleteRollback(rollback);
+                    } else if (session.isStagedSessionApplied()) {
+                        makeRollbackAvailable(rollback);
+                    }
                 }
             }
 
             for (Rollback rollback : restoreInProgress) {
                 PackageInstaller installer = mContext.getPackageManager().getPackageInstaller();
-                PackageInstaller.SessionInfo session = installer.getSessionInfo(
-                        rollback.stagedSessionId);
-                // TODO: What if session is null?
-                if (session != null) {
-                    if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
-                        synchronized (mLock) {
-                            rollback.restoreUserDataInProgress = false;
+                synchronized (rollback.getLock()) {
+                    PackageInstaller.SessionInfo session =
+                            installer.getSessionInfo(rollback.getStagedSessionId());
+                    // TODO: What if session is null?
+                    if (session != null) {
+                        if (session.isStagedSessionApplied() || session.isStagedSessionFailed()) {
+                            rollback.setRestoreUserDataInProgress(false);
+                            saveRollback(rollback);
                         }
-                        saveRollback(rollback);
                     }
                 }
             }
@@ -655,6 +687,11 @@
                 // hasn't actually been updated.
                 onPackageReplaced(apexPackageName);
             }
+
+            synchronized (mLock) {
+                mOrphanedApkSessionIds.clear();
+            }
+
             mPackageHealthObserver.onBootCompletedAsync();
         });
     }
@@ -667,24 +704,19 @@
     private void onPackageReplaced(String packageName) {
         // TODO: Could this end up incorrectly deleting a rollback for a
         // package that is about to be installed?
-        VersionedPackage installedVersion = getInstalledPackageVersion(packageName);
+        long installedVersion = getInstalledPackageVersion(packageName);
 
         synchronized (mLock) {
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                // TODO: Should we remove rollbacks in the ENABLING state here?
-                if (rollback.state == Rollback.ROLLBACK_STATE_AVAILABLE
-                        || rollback.state == Rollback.ROLLBACK_STATE_ENABLING) {
-                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                        if (info.getPackageName().equals(packageName)
-                                && !packageVersionsEqual(
-                                    info.getVersionRolledBackFrom(),
-                                    installedVersion)) {
-                            iter.remove();
-                            deleteRollback(rollback);
-                            break;
-                        }
+                synchronized (rollback.getLock()) {
+                    // TODO: Should we remove rollbacks in the ENABLING state here?
+                    if ((rollback.isEnabling() || rollback.isAvailable())
+                            && rollback.includesPackageWithDifferentVersion(packageName,
+                            installedVersion)) {
+                        iter.remove();
+                        deleteRollback(rollback);
                     }
                 }
             }
@@ -741,15 +773,18 @@
             Iterator<Rollback> iter = mRollbacks.iterator();
             while (iter.hasNext()) {
                 Rollback rollback = iter.next();
-                if (rollback.state != Rollback.ROLLBACK_STATE_AVAILABLE) {
-                    continue;
-                }
-                if (!now.isBefore(
-                            rollback.timestamp.plusMillis(mRollbackLifetimeDurationInMillis))) {
-                    iter.remove();
-                    deleteRollback(rollback);
-                } else if (oldest == null || oldest.isAfter(rollback.timestamp)) {
-                    oldest = rollback.timestamp;
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isAvailable()) {
+                        continue;
+                    }
+                    if (!now.isBefore(
+                            rollback.getTimestamp()
+                                    .plusMillis(mRollbackLifetimeDurationInMillis))) {
+                        iter.remove();
+                        deleteRollback(rollback);
+                    } else if (oldest == null || oldest.isAfter(rollback.getTimestamp())) {
+                        oldest = rollback.getTimestamp();
+                    }
                 }
             }
         }
@@ -857,14 +892,26 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.apkSessionId == parentSession.getSessionId()) {
-                    // This is the apk session for a staged session with rollback enabled. We do not
-                    // need to create a new rollback for this session.
-                    return true;
+                synchronized (rollback.getLock()) {
+                    if (rollback.getApkSessionId() == parentSession.getSessionId()) {
+                        // This is the apk session for a staged session with rollback enabled. We do
+                        // not need to create a new rollback for this session.
+                        return true;
+                    }
                 }
             }
         }
 
+        // Check to see if this is the apk session for a staged session for which rollback was
+        // cancelled.
+        synchronized (mLock) {
+            if (mOrphanedApkSessionIds.indexOf(parentSession.getSessionId()) != -1) {
+                Slog.w(TAG, "Not enabling rollback for apk as no matching staged session "
+                        + "rollback exists");
+                return false;
+            }
+        }
+
         NewRollback newRollback;
         synchronized (mLock) {
             // See if we already have a NewRollback that contains this package
@@ -949,6 +996,7 @@
                 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
                 isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */);
 
+
         try {
             ApplicationInfo appInfo = pkgInfo.applicationInfo;
             RollbackStore.backupPackageCodePath(rollback, packageName, appInfo.sourceDir);
@@ -962,7 +1010,7 @@
             return false;
         }
 
-        synchronized (mLock) {
+        synchronized (rollback.getLock()) {
             rollback.info.getPackages().add(packageRollbackInfo);
         }
         return true;
@@ -990,27 +1038,31 @@
             // staged installs
             for (int i = 0; i < mRollbacks.size(); i++) {
                 Rollback rollback = mRollbacks.get(i);
-                if (rollback.state != Rollback.ROLLBACK_STATE_ENABLING) {
-                    continue;
-                }
+                synchronized (rollback.getLock()) {
+                    if (!rollback.isEnabling()) {
+                        continue;
+                    }
 
-                for (PackageRollbackInfo info : rollback.info.getPackages()) {
-                    if (info.getPackageName().equals(packageName)) {
-                        mAppDataRollbackHelper.snapshotAppData(
-                                rollback.info.getRollbackId(), info, userIds);
-                        saveRollback(rollback);
-                        break;
+                    for (PackageRollbackInfo info : rollback.info.getPackages()) {
+                        if (info.getPackageName().equals(packageName)) {
+                            mAppDataRollbackHelper.snapshotAppData(
+                                    rollback.info.getRollbackId(), info, userIds);
+                            saveRollback(rollback);
+                            break;
+                        }
                     }
                 }
             }
             // non-staged installs
             PackageRollbackInfo info;
             for (NewRollback rollback : mNewRollbacks) {
-                info = getPackageRollbackInfo(rollback.rollback, packageName);
-                if (info != null) {
-                    mAppDataRollbackHelper.snapshotAppData(
-                            rollback.rollback.info.getRollbackId(), info, userIds);
-                    saveRollback(rollback.rollback);
+                synchronized (rollback.rollback.getLock()) {
+                    info = getPackageRollbackInfo(rollback.rollback, packageName);
+                    if (info != null) {
+                        mAppDataRollbackHelper.snapshotAppData(
+                                rollback.rollback.info.getRollbackId(), info, userIds);
+                        saveRollback(rollback.rollback);
+                    }
                 }
             }
         }
@@ -1023,11 +1075,13 @@
         synchronized (mLock) {
             for (int i = 0; i < mRollbacks.size(); ++i) {
                 Rollback candidate = mRollbacks.get(i);
-                if (candidate.restoreUserDataInProgress) {
-                    info = getPackageRollbackInfo(candidate, packageName);
-                    if (info != null) {
-                        rollback = candidate;
-                        break;
+                synchronized (candidate.getLock()) {
+                    if (candidate.isRestoreUserDataInProgress()) {
+                        info = getPackageRollbackInfo(candidate, packageName);
+                        if (info != null) {
+                            rollback = candidate;
+                            break;
+                        }
                     }
                 }
             }
@@ -1038,12 +1092,14 @@
         }
 
         for (int userId : userIds) {
-            final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
-                    rollback.info.getRollbackId(), info, userId, appId, seInfo);
+            synchronized (rollback.getLock()) {
+                final boolean changedRollback = mAppDataRollbackHelper.restoreAppData(
+                        rollback.info.getRollbackId(), info, userId, appId, seInfo);
 
-            // We've updated metadata about this rollback, so save it to flash.
-            if (changedRollback) {
-                saveRollback(rollback);
+                // We've updated metadata about this rollback, so save it to flash.
+                if (changedRollback) {
+                    saveRollback(rollback);
+                }
             }
         }
     }
@@ -1116,16 +1172,25 @@
             synchronized (mLock) {
                 for (int i = 0; i < mRollbacks.size(); ++i) {
                     Rollback candidate = mRollbacks.get(i);
-                    if (candidate.stagedSessionId == originalSessionId) {
-                        candidate.apkSessionId = apkSessionId;
+                    if (candidate.getStagedSessionId() == originalSessionId) {
                         rollback = candidate;
                         break;
                     }
                 }
+                if (rollback == null) {
+                    // Did not find rollback matching originalSessionId.
+                    Slog.e(TAG, "notifyStagedApkSession did not find rollback for session "
+                            + originalSessionId
+                            + ". Adding orphaned apk session " + apkSessionId);
+                    mOrphanedApkSessionIds.add(apkSessionId);
+                }
             }
 
             if (rollback != null) {
-                saveRollback(rollback);
+                synchronized (rollback.getLock()) {
+                    rollback.setApkSessionId(apkSessionId);
+                    saveRollback(rollback);
+                }
             }
         });
     }
@@ -1170,18 +1235,18 @@
 
     /**
      * Gets the version of the package currently installed.
-     * Returns null if the package is not currently installed.
+     * Returns -1 if the package is not currently installed.
      */
-    private VersionedPackage getInstalledPackageVersion(String packageName) {
+    private long getInstalledPackageVersion(String packageName) {
         PackageManager pm = mContext.getPackageManager();
         PackageInfo pkgInfo = null;
         try {
             pkgInfo = getPackageInfo(packageName);
         } catch (PackageManager.NameNotFoundException e) {
-            return null;
+            return -1;
         }
 
-        return new VersionedPackage(packageName, pkgInfo.getLongVersionCode());
+        return pkgInfo.getLongVersionCode();
     }
 
     /**
@@ -1236,44 +1301,49 @@
 
             if (newRollback != null) {
                 Rollback rollback = completeEnableRollback(newRollback, success);
-                if (rollback != null && !rollback.isStaged()) {
-                    makeRollbackAvailable(rollback);
+                if (rollback != null) {
+                    synchronized (rollback.getLock()) {
+                        if (!rollback.isStaged()) {
+                            makeRollbackAvailable(rollback);
+                        }
+                    }
                 }
             }
         }
     }
 
     /**
-     * Add a rollback to the list of rollbacks.
-     * This should be called after rollback has been enabled for all packages
-     * in the rollback. It does not make the rollback available yet.
+     * Add a rollback to the list of rollbacks. This should be called after rollback has been
+     * enabled for all packages in the rollback. It does not make the rollback available yet.
+     *
+     * <p>Note that no rollback-specific locks should be held when this method is called.
      *
      * @return the Rollback instance for a successfully enable-completed rollback,
      * or null on error.
      */
     private Rollback completeEnableRollback(NewRollback newRollback, boolean success) {
         Rollback rollback = newRollback.rollback;
-        if (!success) {
-            // The install session was aborted, clean up the pending install.
-            deleteRollback(rollback);
-            return null;
-        }
-        if (newRollback.isCancelled) {
-            Slog.e(TAG, "Rollback has been cancelled by PackageManager");
-            deleteRollback(rollback);
-            return null;
-        }
+        synchronized (rollback.getLock()) {
+            if (!success) {
+                // The install session was aborted, clean up the pending install.
+                deleteRollback(rollback);
+                return null;
+            }
+            if (newRollback.isCancelled) {
+                Slog.e(TAG, "Rollback has been cancelled by PackageManager");
+                deleteRollback(rollback);
+                return null;
+            }
 
-        // It's safe to access rollback.info outside a synchronized block because
-        // this is running on the handler thread and all changes to the
-        // rollback.info occur on the handler thread.
-        if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
-            Slog.e(TAG, "Failed to enable rollback for all packages in session.");
-            deleteRollback(rollback);
-            return null;
-        }
 
-        saveRollback(rollback);
+            if (rollback.info.getPackages().size() != newRollback.packageSessionIds.length) {
+                Slog.e(TAG, "Failed to enable rollback for all packages in session.");
+                deleteRollback(rollback);
+                return null;
+            }
+
+            saveRollback(rollback);
+        }
         synchronized (mLock) {
             // Note: There is a small window of time between when
             // the session has been committed by the package
@@ -1291,14 +1361,13 @@
         return rollback;
     }
 
+    @GuardedBy("rollback.getLock")
     private void makeRollbackAvailable(Rollback rollback) {
         // TODO: What if the rollback has since been expired, for example due
         // to a new package being installed. Won't this revive an expired
         // rollback? Consider adding a ROLLBACK_STATE_EXPIRED to address this.
-        synchronized (mLock) {
-            rollback.state = Rollback.ROLLBACK_STATE_AVAILABLE;
-            rollback.timestamp = Instant.now();
-        }
+        rollback.setAvailable();
+        rollback.setTimestamp(Instant.now());
         saveRollback(rollback);
 
         // TODO(zezeozue): Provide API to explicitly start observing instead
@@ -1306,11 +1375,7 @@
         // should document in PackageInstaller.SessionParams#setEnableRollback
         // After enabling and commiting any rollback, observe packages and
         // prepare to rollback if packages crashes too frequently.
-        List<String> packages = new ArrayList<>();
-        for (int i = 0; i < rollback.info.getPackages().size(); i++) {
-            packages.add(rollback.info.getPackages().get(i).getPackageName());
-        }
-        mPackageHealthObserver.startObservingHealth(packages,
+        mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
                 mRollbackLifetimeDurationInMillis);
         scheduleExpiration(mRollbackLifetimeDurationInMillis);
     }
@@ -1335,6 +1400,7 @@
      * Returns the {@code PackageRollbackInfo} associated with {@code packageName} from
      * a specified {@code Rollback}.
      */
+    @GuardedBy("rollback.getLock")
     private static PackageRollbackInfo getPackageRollbackInfo(Rollback rollback,
             String packageName) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
@@ -1361,6 +1427,7 @@
         throw new IllegalStateException("Failed to allocate rollback ID");
     }
 
+    @GuardedBy("rollback.getLock")
     private void deleteRollback(Rollback rollback) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
             IntArray snapshottedUsers = info.getSnapshottedUsers();
@@ -1379,6 +1446,7 @@
      * TODO: Double check we can't do a better job handling the IOException in
      * a cases where this method is called.
      */
+    @GuardedBy("rollback.getLock")
     private void saveRollback(Rollback rollback) {
         try {
             mRollbackStore.saveRollback(rollback);
@@ -1393,32 +1461,34 @@
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         synchronized (mLock) {
             for (Rollback rollback : mRollbacks) {
-                RollbackInfo info = rollback.info;
-                ipw.println(info.getRollbackId() + ":");
-                ipw.increaseIndent();
-                ipw.println("-state: " + rollback.getStateAsString());
-                ipw.println("-timestamp: " + rollback.timestamp);
-                if (rollback.stagedSessionId != -1) {
-                    ipw.println("-stagedSessionId: " + rollback.stagedSessionId);
-                }
-                ipw.println("-packages:");
-                ipw.increaseIndent();
-                for (PackageRollbackInfo pkg : info.getPackages()) {
-                    ipw.println(pkg.getPackageName()
-                            + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
-                            + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
-                }
-                ipw.decreaseIndent();
-                if (rollback.state == Rollback.ROLLBACK_STATE_COMMITTED) {
-                    ipw.println("-causePackages:");
+                synchronized (rollback.getLock()) {
+                    RollbackInfo info = rollback.info;
+                    ipw.println(info.getRollbackId() + ":");
                     ipw.increaseIndent();
-                    for (VersionedPackage cPkg : info.getCausePackages()) {
-                        ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                    ipw.println("-state: " + rollback.getStateAsString());
+                    ipw.println("-timestamp: " + rollback.getTimestamp());
+                    if (rollback.getStagedSessionId() != -1) {
+                        ipw.println("-stagedSessionId: " + rollback.getStagedSessionId());
+                    }
+                    ipw.println("-packages:");
+                    ipw.increaseIndent();
+                    for (PackageRollbackInfo pkg : info.getPackages()) {
+                        ipw.println(pkg.getPackageName()
+                                + " " + pkg.getVersionRolledBackFrom().getLongVersionCode()
+                                + " -> " + pkg.getVersionRolledBackTo().getLongVersionCode());
                     }
                     ipw.decreaseIndent();
-                    ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    if (rollback.isCommitted()) {
+                        ipw.println("-causePackages:");
+                        ipw.increaseIndent();
+                        for (VersionedPackage cPkg : info.getCausePackages()) {
+                            ipw.println(cPkg.getPackageName() + " " + cPkg.getLongVersionCode());
+                        }
+                        ipw.decreaseIndent();
+                        ipw.println("-committedSessionId: " + info.getCommittedSessionId());
+                    }
+                    ipw.decreaseIndent();
                 }
-                ipw.decreaseIndent();
             }
         }
     }
@@ -1479,7 +1549,8 @@
         }
     }
 
-    NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
+    @GuardedBy("mLock")
+    private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
         int rollbackId = allocateRollbackIdLocked();
         final Rollback rollback;
         int parentSessionId = parentSession.getSessionId();
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index b2448f6..b6d1f18 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -17,7 +17,6 @@
 package com.android.server.rollback;
 
 import static com.android.server.rollback.Rollback.rollbackStateFromString;
-import static com.android.server.rollback.Rollback.rollbackStateToString;
 
 import android.annotation.NonNull;
 import android.content.pm.VersionedPackage;
@@ -28,6 +27,8 @@
 import android.util.Slog;
 import android.util.SparseLongArray;
 
+import com.android.internal.annotations.GuardedBy;
+
 import libcore.io.IoUtils;
 
 import org.json.JSONArray;
@@ -216,7 +217,7 @@
     static void backupPackageCodePath(Rollback rollback, String packageName, String codePath)
             throws IOException {
         File sourceFile = new File(codePath);
-        File targetDir = new File(rollback.backupDir, packageName);
+        File targetDir = new File(rollback.getBackupDir(), packageName);
         targetDir.mkdirs();
         File targetFile = new File(targetDir, sourceFile.getName());
 
@@ -229,7 +230,7 @@
      * Includes the base apk and any splits. Returns null if none found.
      */
     static File[] getPackageCodePaths(Rollback rollback, String packageName) {
-        File targetDir = new File(rollback.backupDir, packageName);
+        File targetDir = new File(rollback.getBackupDir(), packageName);
         File[] files = targetDir.listFiles();
         if (files == null || files.length == 0) {
             return null;
@@ -243,7 +244,7 @@
      */
     static void deletePackageCodePaths(Rollback rollback) {
         for (PackageRollbackInfo info : rollback.info.getPackages()) {
-            File targetDir = new File(rollback.backupDir, info.getPackageName());
+            File targetDir = new File(rollback.getBackupDir(), info.getPackageName());
             removeFile(targetDir);
         }
     }
@@ -251,17 +252,18 @@
     /**
      * Saves the given rollback to persistent storage.
      */
+    @GuardedBy("rollback.getLock")
     void saveRollback(Rollback rollback) throws IOException {
         try {
             JSONObject dataJson = new JSONObject();
             dataJson.put("info", rollbackInfoToJson(rollback.info));
-            dataJson.put("timestamp", rollback.timestamp.toString());
-            dataJson.put("stagedSessionId", rollback.stagedSessionId);
-            dataJson.put("state", rollbackStateToString(rollback.state));
-            dataJson.put("apkSessionId", rollback.apkSessionId);
-            dataJson.put("restoreUserDataInProgress", rollback.restoreUserDataInProgress);
+            dataJson.put("timestamp", rollback.getTimestamp().toString());
+            dataJson.put("stagedSessionId", rollback.getStagedSessionId());
+            dataJson.put("state", rollback.getStateAsString());
+            dataJson.put("apkSessionId", rollback.getApkSessionId());
+            dataJson.put("restoreUserDataInProgress", rollback.isRestoreUserDataInProgress());
 
-            PrintWriter pw = new PrintWriter(new File(rollback.backupDir, "rollback.json"));
+            PrintWriter pw = new PrintWriter(new File(rollback.getBackupDir(), "rollback.json"));
             pw.println(dataJson.toString());
             pw.close();
         } catch (JSONException e) {
@@ -273,7 +275,7 @@
      * Removes all persistent storage associated with the given rollback.
      */
     void deleteRollback(Rollback rollback) {
-        removeFile(rollback.backupDir);
+        removeFile(rollback.getBackupDir());
     }
 
     /**
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
index b1db46f..856a40f 100644
--- a/services/core/java/com/android/server/security/VerityUtils.java
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -26,27 +26,19 @@
 import android.util.apk.ApkSignatureVerifier;
 import android.util.apk.ByteBufferFactory;
 import android.util.apk.SignatureNotFoundException;
-import android.util.apk.VerityBuilder;
 
 import libcore.util.HexEncoding;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.FileChannel;
 import java.nio.file.Files;
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.security.DigestException;
-import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 
-import sun.security.pkcs.PKCS7;
-
 /** Provides fsverity related operations. */
 abstract public class VerityUtils {
     private static final String TAG = "VerityUtils";
@@ -60,8 +52,6 @@
     /** The maximum size of signature file.  This is just to avoid potential abuse. */
     private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192;
 
-    private static final int COMMON_LINUX_PAGE_SIZE_IN_BYTES = 4096;
-
     private static final boolean DEBUG = false;
 
     /** Returns true if the given file looks like containing an fs-verity signature. */
@@ -74,42 +64,15 @@
         return filePath + FSVERITY_SIGNATURE_FILE_EXTENSION;
     }
 
-    /** Generates Merkle tree and fs-verity metadata then enables fs-verity. */
-    public static void setUpFsverity(@NonNull String filePath, String signaturePath)
-            throws IOException, DigestException, NoSuchAlgorithmException {
-        final PKCS7 pkcs7 = new PKCS7(Files.readAllBytes(Paths.get(signaturePath)));
-        final byte[] expectedMeasurement = pkcs7.getContentInfo().getContentBytes();
-        if (DEBUG) {
-            Slog.d(TAG, "Enabling fs-verity with signed fs-verity measurement "
-                    + bytesToString(expectedMeasurement));
-            Slog.d(TAG, "PKCS#7 info: " + pkcs7);
+    /** Enables fs-verity for the file with a PKCS#7 detached signature file. */
+    public static void setUpFsverity(@NonNull String filePath, @NonNull String signaturePath)
+            throws IOException {
+        if (Files.size(Paths.get(signaturePath)) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
+            throw new SecurityException("Signature file is unexpectedly large: " + signaturePath);
         }
-
-        final TrackedBufferFactory bufferFactory = new TrackedBufferFactory();
-        final byte[] actualMeasurement = generateFsverityMetadata(filePath, signaturePath,
-                bufferFactory);
-        try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
-            FileChannel ch = raf.getChannel();
-            ch.position(roundUpToNextMultiple(ch.size(), COMMON_LINUX_PAGE_SIZE_IN_BYTES));
-            ByteBuffer buffer = bufferFactory.getBuffer();
-
-            long offset = buffer.position();
-            long size = buffer.limit();
-            while (offset < size) {
-                long s = ch.write(buffer);
-                offset += s;
-                size -= s;
-            }
-        }
-
-        if (!Arrays.equals(expectedMeasurement, actualMeasurement)) {
-            throw new SecurityException("fs-verity measurement mismatch: "
-                    + bytesToString(actualMeasurement) + " != "
-                    + bytesToString(expectedMeasurement));
-        }
-
-        // This can fail if the public key is not already in .fs-verity kernel keyring.
-        int errno = enableFsverityNative(filePath);
+        byte[] pkcs7Signature = Files.readAllBytes(Paths.get(signaturePath));
+        // This will fail if the public key is not already in .fs-verity kernel keyring.
+        int errno = enableFsverityNative(filePath, pkcs7Signature);
         if (errno != 0) {
             throw new IOException("Failed to enable fs-verity on " + filePath + ": "
                     + Os.strerror(errno));
@@ -131,12 +94,19 @@
         return true;
     }
 
+    private static native int enableFsverityNative(@NonNull String filePath,
+            @NonNull byte[] pkcs7Signature);
+    private static native int measureFsverityNative(@NonNull String filePath);
+
     /**
      * Generates legacy Merkle tree and fs-verity metadata with Signing Block skipped.
      *
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      * @return {@code SetupResult} that contains the result code, and when success, the
      *         {@code FileDescriptor} to read all the data from.
      */
+    @Deprecated
     public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
         if (DEBUG) {
             Slog.d(TAG, "Trying to install legacy apk verity to " + apkPath);
@@ -173,7 +143,10 @@
 
     /**
      * {@see ApkSignatureVerifier#generateApkVerityRootHash(String)}.
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      */
+    @Deprecated
     public static byte[] generateApkVerityRootHash(@NonNull String apkPath)
             throws NoSuchAlgorithmException, DigestException, IOException {
         return ApkSignatureVerifier.generateApkVerityRootHash(apkPath);
@@ -181,104 +154,16 @@
 
     /**
      * {@see ApkSignatureVerifier#getVerityRootHash(String)}.
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
      */
+    @Deprecated
     public static byte[] getVerityRootHash(@NonNull String apkPath)
             throws IOException, SignatureNotFoundException {
         return ApkSignatureVerifier.getVerityRootHash(apkPath);
     }
 
     /**
-     * Generates fs-verity metadata for {@code filePath} in the buffer created by {@code
-     * trackedBufferFactory}. The metadata contains the Merkle tree, fs-verity descriptor and
-     * extensions, including a PKCS#7 signature provided in {@code signaturePath}.
-     *
-     * <p>It is worthy to note that {@code trackedBufferFactory} generates a "tracked" {@code
-     * ByteBuffer}. The data will be used outside this method via the factory itself.
-     *
-     * @return fs-verity signed data (struct fsverity_digest_disk) of {@code filePath}, which
-     *         includes SHA-256 of fs-verity descriptor and authenticated extensions.
-     */
-    private static byte[] generateFsverityMetadata(String filePath, String signaturePath,
-            @NonNull ByteBufferFactory trackedBufferFactory)
-            throws IOException, DigestException, NoSuchAlgorithmException {
-        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
-            VerityBuilder.VerityResult result = VerityBuilder.generateFsVerityTree(
-                    file, trackedBufferFactory);
-
-            ByteBuffer buffer = result.verityData;
-            buffer.position(result.merkleTreeSize);
-
-            final byte[] measurement = generateFsverityDescriptorAndMeasurement(file,
-                    result.rootHash, signaturePath, buffer);
-            buffer.flip();
-            return constructFsveritySignedDataNative(measurement);
-        }
-    }
-
-    /**
-     * Generates fs-verity descriptor including the extensions to the {@code output} and returns the
-     * fs-verity measurement.
-     *
-     * @return fs-verity measurement, which is a SHA-256 of fs-verity descriptor and authenticated
-     *         extensions.
-     */
-    private static byte[] generateFsverityDescriptorAndMeasurement(
-            @NonNull RandomAccessFile file, @NonNull byte[] rootHash,
-            @NonNull String pkcs7SignaturePath, @NonNull ByteBuffer output)
-            throws IOException, NoSuchAlgorithmException, DigestException {
-        final short kRootHashExtensionId = 1;
-        final short kPkcs7SignatureExtensionId = 3;
-        final int origPosition = output.position();
-
-        // For generating fs-verity file measurement, which consists of the descriptor and
-        // authenticated extensions (but not unauthenticated extensions and the footer).
-        MessageDigest md = MessageDigest.getInstance("SHA-256");
-
-        // 1. Generate fs-verity descriptor.
-        final byte[] desc = constructFsverityDescriptorNative(file.length());
-        output.put(desc);
-        md.update(desc);
-
-        // 2. Generate authenticated extensions.
-        final byte[] authExt =
-                constructFsverityExtensionNative(kRootHashExtensionId, rootHash.length);
-        output.put(authExt);
-        output.put(rootHash);
-        md.update(authExt);
-        md.update(rootHash);
-
-        // 3. Generate unauthenticated extensions.
-        ByteBuffer header = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
-        output.putShort((short) 1);  // number of unauthenticated extensions below
-        output.position(output.position() + 6);
-
-        // Generate PKCS#7 extension. NB: We do not verify agaist trusted certificate (should be
-        // done by the caller if needed).
-        Path path = Paths.get(pkcs7SignaturePath);
-        if (Files.size(path) > MAX_SIGNATURE_FILE_SIZE_BYTES) {
-            throw new IllegalArgumentException("Signature size is unexpectedly large: "
-                    + pkcs7SignaturePath);
-        }
-        final byte[] pkcs7Signature = Files.readAllBytes(path);
-        output.put(constructFsverityExtensionNative(kPkcs7SignatureExtensionId,
-                    pkcs7Signature.length));
-        output.put(pkcs7Signature);
-
-        // 4. Generate the footer.
-        output.put(constructFsverityFooterNative(output.position() - origPosition));
-
-        return md.digest();
-    }
-
-    private static native int enableFsverityNative(@NonNull String filePath);
-    private static native int measureFsverityNative(@NonNull String filePath);
-    private static native byte[] constructFsveritySignedDataNative(@NonNull byte[] measurement);
-    private static native byte[] constructFsverityDescriptorNative(long fileSize);
-    private static native byte[] constructFsverityExtensionNative(short extensionId,
-            int extensionDataSize);
-    private static native byte[] constructFsverityFooterNative(int offsetToDescriptorHead);
-
-    /**
      * Returns a pair of {@code SharedMemory} and {@code Integer}. The {@code SharedMemory} contains
      * Merkle tree and fsverity headers for the given apk, in the form that can immediately be used
      * for fsverity setup. The data is aligned to the beginning of {@code SharedMemory}, and has
@@ -313,6 +198,11 @@
         return HexEncoding.encodeToString(bytes);
     }
 
+    /**
+     * @deprecated This is only used for previous fs-verity implementation, and should never be used
+     *             on new devices.
+     */
+    @Deprecated
     public static class SetupResult {
         /** Result code if verity is set up correctly. */
         private static final int RESULT_OK = 1;
@@ -401,30 +291,4 @@
             return mBuffer == null ? -1 : mBuffer.limit();
         }
     }
-
-    /** A {@code ByteBufferFactory} that tracks the {@code ByteBuffer} it creates. */
-    private static class TrackedBufferFactory implements ByteBufferFactory {
-        private ByteBuffer mBuffer;
-
-        @Override
-        public ByteBuffer create(int capacity) {
-            if (mBuffer != null) {
-                throw new IllegalStateException("Multiple instantiation from this factory");
-            }
-            mBuffer = ByteBuffer.allocate(capacity);
-            return mBuffer;
-        }
-
-        public ByteBuffer getBuffer() {
-            return mBuffer;
-        }
-    }
-
-    /** Round up the number to the next multiple of the divisor. */
-    private static long roundUpToNextMultiple(long number, long divisor) {
-        if (number > (Long.MAX_VALUE - divisor)) {
-            throw new IllegalArgumentException("arithmetic overflow");
-        }
-        return ((number + (divisor - 1)) / divisor) * divisor;
-    }
 }
diff --git a/services/core/java/com/android/server/stats/IonMemoryUtil.java b/services/core/java/com/android/server/stats/IonMemoryUtil.java
new file mode 100644
index 0000000..c9be96f
--- /dev/null
+++ b/services/core/java/com/android/server/stats/IonMemoryUtil.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Utility methods for reading ion memory stats. */
+final class IonMemoryUtil {
+    private static final String TAG = "IonMemoryUtil";
+
+    /** Path to debugfs file for the system ion heap. */
+    private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
+
+    private static final Pattern ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
+    private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES =
+            Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)");
+
+    private IonMemoryUtil() {}
+
+    /**
+     * Reads size of the system ion heap from debugfs.
+     *
+     * Returns value of the total size in bytes of the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    static long readSystemIonHeapSizeFromDebugfs() {
+        return parseIonHeapSizeFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
+     * debugfs. The returned value is in bytes.
+     */
+    @VisibleForTesting
+    static long parseIonHeapSizeFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        try {
+            return matcher.find() ? Long.parseLong(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Reads process allocation sizes on the system ion heap from debugfs.
+     *
+     * Returns values of allocation sizes in bytes on the system ion heap from
+     * /sys/kernel/debug/ion/heaps/system.
+     */
+    static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
+        return parseProcessIonHeapSizesFromDebugfs(readFile(DEBUG_SYSTEM_ION_HEAP_FILE));
+    }
+
+    /**
+     * Parses per-process allocation sizes on the ion heap from the contents of a file under
+     * /sys/kernel/debug/ion/heaps in debugfs.
+     */
+    @VisibleForTesting
+    static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) {
+        if (contents.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents);
+        final SparseArray<IonAllocations> entries = new SparseArray<>();
+        while (m.find()) {
+            try {
+                final int pid = Integer.parseInt(m.group(1));
+                final long sizeInBytes = Long.parseLong(m.group(2));
+                IonAllocations allocations = entries.get(pid);
+                if (allocations == null) {
+                    allocations = new IonAllocations();
+                    entries.put(pid, allocations);
+                }
+                allocations.pid = pid;
+                allocations.totalSizeInBytes += sizeInBytes;
+                allocations.count += 1;
+                allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes);
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Failed to parse value", e);
+            }
+        }
+
+        final List<IonAllocations> result = new ArrayList<>(entries.size());
+        for (int i = 0; i < entries.size(); i++) {
+            result.add(entries.valueAt(i));
+        }
+        return result;
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read file", e);
+            return "";
+        }
+    }
+
+    /** Summary information about process ion allocations. */
+    static final class IonAllocations {
+        /** PID these allocations belong to. */
+        public int pid;
+        /** Size of all individual allocations added together. */
+        public long totalSizeInBytes;
+        /** Number of allocations. */
+        public int count;
+        /** Size of the largest allocation. */
+        public long maxSizeInBytes;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            IonAllocations that = (IonAllocations) o;
+            return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes
+                    && count == that.count && maxSizeInBytes == that.maxSizeInBytes;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
+        }
+
+        @Override
+        public String toString() {
+            return "IonAllocations{"
+                    + "pid=" + pid
+                    + ", totalSizeInBytes=" + totalSizeInBytes
+                    + ", count=" + count
+                    + ", maxSizeInBytes=" + maxSizeInBytes
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
new file mode 100644
index 0000000..d49b958
--- /dev/null
+++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.os.FileUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class ProcfsMemoryUtil {
+    private static final String TAG = "ProcfsMemoryUtil";
+
+    /** Path to procfs status file: /proc/pid/status. */
+    private static final String STATUS_FILE_FMT = "/proc/%d/status";
+
+    private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES =
+            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
+    private static final Pattern RSS_IN_KILOBYTES =
+            Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB");
+    private static final Pattern ANON_RSS_IN_KILOBYTES =
+            Pattern.compile("RssAnon:\\s*(\\d+)\\s*kB");
+    private static final Pattern SWAP_IN_KILOBYTES =
+            Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB");
+
+    private ProcfsMemoryUtil() {}
+
+    /**
+     * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
+     * /proc/PID/status in kilobytes or 0 if not available.
+     */
+    static int readRssHighWaterMarkFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseVmHWMFromStatus(readFile(statusPath));
+    }
+
+    /**
+     * Parses RSS high-water mark out from the contents of the /proc/pid/status file in procfs. The
+     * returned value is in kilobytes.
+     */
+    @VisibleForTesting
+    static int parseVmHWMFromStatus(String contents) {
+        return tryParseInt(contents, RSS_HIGH_WATER_MARK_IN_KILOBYTES);
+    }
+
+    /**
+     * Reads memory stat of a process from procfs. Returns values of the VmRss, AnonRSS, VmSwap
+     * fields in /proc/pid/status in kilobytes or 0 if not available.
+     */
+    static MemorySnapshot readMemorySnapshotFromProcfs(int pid) {
+        final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid);
+        return parseMemorySnapshotFromStatus(readFile(statusPath));
+    }
+
+    @VisibleForTesting
+    static MemorySnapshot parseMemorySnapshotFromStatus(String contents) {
+        final MemorySnapshot snapshot = new MemorySnapshot();
+        snapshot.rssInKilobytes = tryParseInt(contents, RSS_IN_KILOBYTES);
+        snapshot.anonRssInKilobytes = tryParseInt(contents, ANON_RSS_IN_KILOBYTES);
+        snapshot.swapInKilobytes = tryParseInt(contents, SWAP_IN_KILOBYTES);
+        return snapshot;
+    }
+
+    private static String readFile(String path) {
+        try {
+            final File file = new File(path);
+            return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
+        } catch (IOException e) {
+            return "";
+        }
+    }
+
+    private static int tryParseInt(String contents, Pattern pattern) {
+        if (contents.isEmpty()) {
+            return 0;
+        }
+        final Matcher matcher = pattern.matcher(contents);
+        try {
+            return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0;
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Failed to parse value", e);
+            return 0;
+        }
+    }
+
+    static final class MemorySnapshot {
+        public int rssInKilobytes;
+        public int anonRssInKilobytes;
+        public int swapInKilobytes;
+
+        boolean isEmpty() {
+            return (anonRssInKilobytes + swapInKilobytes) == 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c76bbb0..e1a48ed 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -27,9 +27,10 @@
 import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readProcessSystemIonHeapSizesFromDebugfs;
-import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs;
-import static com.android.server.am.MemoryStatUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -138,9 +139,10 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.am.MemoryStatUtil.IonAllocations;
 import com.android.server.am.MemoryStatUtil.MemoryStat;
 import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
 import com.android.server.storage.DiskStatsFileLogger;
 import com.android.server.storage.DiskStatsLoggingService;
 
@@ -1112,13 +1114,12 @@
             e.writeLong(modemInfo.getTimestamp());
             e.writeLong(modemInfo.getSleepTimeMillis());
             e.writeLong(modemInfo.getIdleTimeMillis());
-            e.writeLong(modemInfo.getTxTimeMillis()[0]);
-            e.writeLong(modemInfo.getTxTimeMillis()[1]);
-            e.writeLong(modemInfo.getTxTimeMillis()[2]);
-            e.writeLong(modemInfo.getTxTimeMillis()[3]);
-            e.writeLong(modemInfo.getTxTimeMillis()[4]);
-            e.writeLong(modemInfo.getRxTimeMillis());
-            e.writeLong(modemInfo.getEnergyUsed());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis());
+            e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis());
+            e.writeLong(modemInfo.getReceiveTimeMillis());
             pulledData.add(e);
         }
     }
@@ -1237,15 +1238,17 @@
                 LocalServices.getService(
                         ActivityManagerInternal.class).getMemoryStateForProcesses();
         for (ProcessMemoryState managedProcess : managedProcessList) {
-            final long rssHighWaterMarkInBytes =
+            final int rssHighWaterMarkInKilobytes =
                     readRssHighWaterMarkFromProcfs(managedProcess.pid);
-            if (rssHighWaterMarkInBytes == 0) {
+            if (rssHighWaterMarkInKilobytes == 0) {
                 continue;
             }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(managedProcess.uid);
             e.writeString(managedProcess.processName);
-            e.writeLong(rssHighWaterMarkInBytes);
+            // RSS high-water mark in bytes.
+            e.writeLong((long) rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(rssHighWaterMarkInKilobytes);
             pulledData.add(e);
         }
         int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
@@ -1253,17 +1256,63 @@
             final int pid = pids[i];
             final int uid = getUidForPid(pid);
             final String processName = readCmdlineFromProcfs(pid);
-            final long rssHighWaterMarkInBytes = readRssHighWaterMarkFromProcfs(pid);
+            final int rssHighWaterMarkInKilobytes = readRssHighWaterMarkFromProcfs(pid);
+            if (rssHighWaterMarkInKilobytes == 0) {
+                continue;
+            }
             StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
             e.writeInt(uid);
             e.writeString(processName);
-            e.writeLong(rssHighWaterMarkInBytes);
+            // RSS high-water mark in bytes.
+            e.writeLong((long) rssHighWaterMarkInKilobytes * 1024L);
+            e.writeInt(rssHighWaterMarkInKilobytes);
             pulledData.add(e);
         }
         // Invoke rss_hwm_reset binary to reset RSS HWM counters for all processes.
         SystemProperties.set("sys.rss_hwm_reset.on", "1");
     }
 
+    private void pullProcessMemorySnapshot(
+            int tagId, long elapsedNanos, long wallClockNanos,
+            List<StatsLogEventWrapper> pulledData) {
+        List<ProcessMemoryState> managedProcessList =
+                LocalServices.getService(
+                        ActivityManagerInternal.class).getMemoryStateForProcesses();
+        for (ProcessMemoryState managedProcess : managedProcessList) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(managedProcess.uid);
+            e.writeString(managedProcess.processName);
+            e.writeInt(managedProcess.pid);
+            e.writeInt(managedProcess.oomScore);
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+        int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES);
+        for (int pid : pids) {
+            StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
+            e.writeInt(getUidForPid(pid));
+            e.writeString(readCmdlineFromProcfs(pid));
+            e.writeInt(pid);
+            e.writeInt(-1001);  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
+            final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid);
+            if (snapshot.isEmpty()) {
+                continue;
+            }
+            e.writeInt(snapshot.rssInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes);
+            e.writeInt(snapshot.swapInKilobytes);
+            e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes);
+            pulledData.add(e);
+        }
+    }
+
     private void pullSystemIonHeapSize(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2346,6 +2395,10 @@
                 pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+            case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
+                pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
+                break;
+            }
             case StatsLog.SYSTEM_ION_HEAP_SIZE: {
                 pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 2df7370..60de10c 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -42,13 +42,13 @@
 import android.os.UserManager;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseLongArray;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.AtomicFile;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.Installer;
diff --git a/services/core/java/com/android/server/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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b0f1e5d..3cdb59b 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -37,6 +37,7 @@
 import android.app.WallpaperColors;
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
+import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.WallpaperBackupHelper;
 import android.content.BroadcastReceiver;
@@ -73,7 +74,6 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -91,9 +91,9 @@
 import android.util.Xml;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.IWindowManager;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
@@ -739,7 +739,6 @@
     }
 
     private final Context mContext;
-    private final IWindowManager mIWindowManager;
     private final WindowManagerInternal mWindowManagerInternal;
     private final IPackageManager mIPackageManager;
     private final MyPackageMonitor mMonitor;
@@ -792,7 +791,7 @@
      */
     private final SparseArray<SparseArray<RemoteCallbackList<IWallpaperManagerCallback>>>
             mColorsChangedListeners;
-    private WallpaperData mLastWallpaper;
+    protected WallpaperData mLastWallpaper;
     private IWallpaperManagerCallback mKeyguardListener;
     private boolean mWaitingForUnlock;
     private boolean mShuttingDown;
@@ -825,7 +824,7 @@
 
     private SparseArray<DisplayData> mDisplayDatas = new SparseArray<>();
 
-    private WallpaperData mFallbackWallpaper;
+    protected WallpaperData mFallbackWallpaper;
 
     private final SparseBooleanArray mUserRestorecon = new SparseBooleanArray();
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -900,9 +899,8 @@
          */
         final Rect cropHint = new Rect(0, 0, 0, 0);
 
-        WallpaperData(int userId, String inputFileName, String cropFileName) {
+        WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) {
             this.userId = userId;
-            final File wallpaperDir = getWallpaperDir(userId);
             wallpaperFile = new File(wallpaperDir, inputFileName);
             cropFile = new File(wallpaperDir, cropFileName);
         }
@@ -917,7 +915,8 @@
         }
     }
 
-    private static final class DisplayData {
+    @VisibleForTesting
+    static final class DisplayData {
         int mWidth = -1;
         int mHeight = -1;
         final Rect mPadding = new Rect(0, 0, 0, 0);
@@ -1057,13 +1056,7 @@
                     return;
                 }
                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
-                try {
-                    mIWindowManager.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed add wallpaper window token on display " + mDisplayId, e);
-                    return;
-                }
-
+                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
                 final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
                 try {
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
@@ -1081,10 +1074,8 @@
 
             void disconnectLocked() {
                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mToken);
-                try {
-                    mIWindowManager.removeWindowToken(mToken, mDisplayId);
-                } catch (RemoteException e) {
-                }
+                mWindowManagerInternal.removeWindowToken(mToken, false/* removeWindows */,
+                        mDisplayId);
                 try {
                     if (mEngine != null) {
                         mEngine.destroy();
@@ -1562,6 +1553,15 @@
         }
     }
 
+    @VisibleForTesting
+    WallpaperData getCurrentWallpaperData(@SetWallpaperFlags int which, int userId) {
+        synchronized (mLock) {
+            final SparseArray<WallpaperData> wallpaperDataMap =
+                    which == FLAG_SYSTEM ? mWallpaperMap : mLockWallpaperMap;
+            return wallpaperDataMap.get(userId);
+        }
+    }
+
     public WallpaperManagerService(Context context) {
         if (DEBUG) Slog.v(TAG, "WallpaperService startup");
         mContext = context;
@@ -1569,8 +1569,6 @@
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
         mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
-        mIWindowManager = IWindowManager.Stub.asInterface(
-                ServiceManager.getService(Context.WINDOW_SERVICE));
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1600,7 +1598,7 @@
         getWallpaperSafeLocked(UserHandle.USER_SYSTEM, FLAG_SYSTEM);
     }
 
-    private static File getWallpaperDir(int userId) {
+    File getWallpaperDir(int userId) {
         return Environment.getUserSystemDirectory(userId);
     }
 
@@ -1819,7 +1817,8 @@
                     // while locked, so pretend like the component was actually
                     // bound into place
                     wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
-                    final WallpaperData fallback = new WallpaperData(wallpaper.userId,
+                    final WallpaperData fallback =
+                            new WallpaperData(wallpaper.userId, getWallpaperDir(wallpaper.userId),
                             WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                     ensureSaneWallpaperData(fallback, DEFAULT_DISPLAY);
                     bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
@@ -2380,7 +2379,7 @@
         }
 
         // We know a-priori that there is no lock-only wallpaper currently
-        WallpaperData lockWP = new WallpaperData(userId,
+        WallpaperData lockWP = new WallpaperData(userId, getWallpaperDir(userId),
                 WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
         lockWP.wallpaperId = sysWP.wallpaperId;
         lockWP.cropHint.set(sysWP.cropHint);
@@ -2793,7 +2792,7 @@
         }
     }
 
-    private static JournaledFile makeJournaledFile(int userId) {
+    private JournaledFile makeJournaledFile(int userId) {
         final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
         return new JournaledFile(new File(base), new File(base + ".tmp"));
     }
@@ -2958,7 +2957,7 @@
             // it now.
             if (wallpaper == null) {
                 if (which == FLAG_LOCK) {
-                    wallpaper = new WallpaperData(userId,
+                    wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
                             WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                     mLockWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY);
@@ -2966,7 +2965,8 @@
                     // sanity fallback: we're in bad shape, but establishing a known
                     // valid system+lock WallpaperData will keep us from dying.
                     Slog.wtf(TAG, "Didn't find wallpaper in non-lock case!");
-                    wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
+                    wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
+                            WALLPAPER, WALLPAPER_CROP);
                     mWallpaperMap.put(userId, wallpaper);
                     ensureSaneWallpaperData(wallpaper, DEFAULT_DISPLAY);
                 }
@@ -2985,7 +2985,8 @@
             // Do this once per boot
             migrateFromOld();
 
-            wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
+            wallpaper = new WallpaperData(userId, getWallpaperDir(userId),
+                    WALLPAPER, WALLPAPER_CROP);
             wallpaper.allowBackup = true;
             mWallpaperMap.put(userId, wallpaper);
             if (!wallpaper.cropExists()) {
@@ -3037,7 +3038,7 @@
                         // keyguard-specific wallpaper for this user
                         WallpaperData lockWallpaper = mLockWallpaperMap.get(userId);
                         if (lockWallpaper == null) {
-                            lockWallpaper = new WallpaperData(userId,
+                            lockWallpaper = new WallpaperData(userId, getWallpaperDir(userId),
                                     WALLPAPER_LOCK_ORIG, WALLPAPER_LOCK_CROP);
                             mLockWallpaperMap.put(userId, lockWallpaper);
                         }
@@ -3088,8 +3089,9 @@
     private void initializeFallbackWallpaper() {
         if (mFallbackWallpaper == null) {
             if (DEBUG) Slog.d(TAG, "Initialize fallback wallpaper");
-            mFallbackWallpaper = new WallpaperData(
-                    UserHandle.USER_SYSTEM, WALLPAPER, WALLPAPER_CROP);
+            final int systemUserId = UserHandle.USER_SYSTEM;
+            mFallbackWallpaper = new WallpaperData(systemUserId, getWallpaperDir(systemUserId),
+                    WALLPAPER, WALLPAPER_CROP);
             mFallbackWallpaper.allowBackup = false;
             mFallbackWallpaper.wallpaperId = makeWallpaperIdLocked();
             bindWallpaperComponentLocked(mImageWallpaper, true, false, mFallbackWallpaper, null);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdater.java b/services/core/java/com/android/server/webkit/WebViewUpdater.java
index a460040..3b58af2 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdater.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdater.java
@@ -87,19 +87,6 @@
                         newPackage = findPreferredWebViewPackage();
                         if (mCurrentWebViewPackage != null) {
                             oldProviderName = mCurrentWebViewPackage.packageName;
-                            if (changedState == WebViewUpdateService.PACKAGE_CHANGED
-                                    && newPackage.packageName.equals(oldProviderName)) {
-                                // If we don't change package name we should only rerun the
-                                // preparation phase if the current package has been replaced
-                                // (not if it has been enabled/disabled).
-                                return;
-                            }
-                            if (newPackage.packageName.equals(oldProviderName)
-                                    && (newPackage.lastUpdateTime
-                                        == mCurrentWebViewPackage.lastUpdateTime)) {
-                                // If the chosen package hasn't been updated, then early-out
-                                return;
-                            }
                         }
                         // Only trigger update actions if the updated package is the one
                         // that will be used, or the one that was in use before the
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 30a3aef..59f051b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -255,13 +255,19 @@
         }
     }
 
-    public void onSomeWindowResizedOrMovedLocked(int displayId) {
+    /**
+     * Called when the location or the size of the window is changed. Moving the window to
+     * another display is also taken into consideration.
+     * @param displayIds the display ids of displays when the situation happens.
+     */
+    public void onSomeWindowResizedOrMovedLocked(int... displayIds) {
         // Not relevant for the display magnifier.
-
-        final WindowsForAccessibilityObserver windowsForA11yObserver =
-                mWindowsForAccessibilityObserver.get(displayId);
-        if (windowsForA11yObserver != null) {
-            windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+        for (int i = 0; i < displayIds.length; i++) {
+            final WindowsForAccessibilityObserver windowsForA11yObserver =
+                    mWindowsForAccessibilityObserver.get(displayIds[i]);
+            if (windowsForA11yObserver != null) {
+                windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
+            }
         }
     }
 
@@ -808,7 +814,7 @@
                 private final Paint mPaint = new Paint();
 
                 private final SurfaceControl mSurfaceControl;
-                private final Surface mSurface = new Surface();
+                private final Surface mSurface = mService.mSurfaceFactory.get();
 
                 private final AnimationController mAnimationController;
 
@@ -961,7 +967,7 @@
                 }
 
                 public void releaseSurface() {
-                    mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
+                    mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
                     mSurface.release();
                 }
 
@@ -1100,14 +1106,10 @@
 
         private final Point mTempPoint = new Point();
 
-        private final Rect mTempRect = new Rect();
-
         private final Region mTempRegion = new Region();
 
         private final Region mTempRegion1 = new Region();
 
-        private final Context mContext;
-
         private final WindowManagerService mService;
 
         private final Handler mHandler;
@@ -1121,7 +1123,6 @@
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 int displayId,
                 WindowsForAccessibilityCallback callback) {
-            mContext = windowManagerService.mContext;
             mService = windowManagerService;
             mCallback = callback;
             mDisplayId = displayId;
@@ -1240,6 +1241,11 @@
         private boolean windowMattersToAccessibility(WindowState windowState,
                 Region regionInScreen, Region unaccountedSpace,
                 HashSet<Integer> skipRemainingWindowsForTasks) {
+            final RecentsAnimationController controller = mService.getRecentsAnimationController();
+            if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
+                return false;
+            }
+
             if (windowState.isFocused()) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e76078f..e488cc9 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -574,11 +574,9 @@
      * then we should explicitly pause that stack's top activity.
      * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
      * @param resuming The resuming activity.
-     * @param dontWait The resuming activity isn't going to wait for all activities to be paused
-     *                 before resuming.
      * @return {@code true} if any activity was paused as a result of this call.
      */
-    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
+    boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming) {
         boolean someActivityPaused = false;
         for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mStacks.get(stackNdx);
@@ -588,8 +586,8 @@
                         || !stack.isFocusable())) {
                 if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                         " mResumedActivity=" + resumedActivity);
-                someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
-                        dontWait);
+                someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
+                        resuming);
             }
         }
         return someActivityPaused;
@@ -742,7 +740,7 @@
     }
 
     private void onSplitScreenModeDismissed() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any stack in secondary split-screen to fullscreen.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -766,12 +764,12 @@
                 mHomeStack.moveToFront("onSplitScreenModeDismissed");
                 topFullscreenStack.moveToFront("onSplitScreenModeDismissed");
             }
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
     private void onSplitScreenModeActivated() {
-        mRootActivityContainer.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Adjust the windowing mode of any affected by split-screen to split-screen secondary.
             for (int i = mStacks.size() - 1; i >= 0; --i) {
@@ -786,7 +784,7 @@
                         false /* creating */);
             }
         } finally {
-            mRootActivityContainer.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1004,12 +1002,9 @@
         Configuration values = new Configuration();
         mDisplayContent.computeScreenConfiguration(values);
 
-        if (mService.mWindowManager != null) {
-            final Message msg = PooledLambda.obtainMessage(
-                    ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
-                    mDisplayId);
-            mService.mH.sendMessage(msg);
-        }
+        mService.mH.sendMessage(PooledLambda.obtainMessage(
+                ActivityManagerInternal::updateOomLevelsForDisplay, mService.mAmInternal,
+                mDisplayId));
 
         Settings.System.clearConfiguration(values);
         updateDisplayOverrideConfigurationLocked(values, null /* starting */,
@@ -1028,9 +1023,7 @@
         int changes = 0;
         boolean kept = true;
 
-        if (mService.mWindowManager != null) {
-            mService.mWindowManager.deferSurfaceLayout();
-        }
+        mService.deferWindowLayout();
         try {
             if (values != null) {
                 if (mDisplayId == DEFAULT_DISPLAY) {
@@ -1047,9 +1040,7 @@
 
             kept = mService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mService.mWindowManager != null) {
-                mService.mWindowManager.continueSurfaceLayout();
-            }
+            mService.continueWindowLayout();
         }
 
         if (result != null) {
@@ -1098,6 +1089,8 @@
             mService.mWindowManager.setNewDisplayOverrideConfiguration(
                     overrideConfiguration, mDisplayContent);
         }
+        mService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 36e906c..c54ccd4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -118,7 +118,6 @@
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP;
@@ -1664,11 +1663,10 @@
     @interface FinishRequest {}
 
     /**
-     * See {@link #finishIfPossible(int, Intent, String, boolean, boolean)}
+     * See {@link #finishIfPossible(int, Intent, String, boolean)}
      */
     @FinishRequest int finishIfPossible(String reason, boolean oomAdj) {
-        return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
-                oomAdj, !PAUSE_IMMEDIATELY);
+        return finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason, oomAdj);
     }
 
     /**
@@ -1683,7 +1681,7 @@
      * request to finish it was not ignored.
      */
     @FinishRequest int finishIfPossible(int resultCode, Intent resultData, String reason,
-            boolean oomAdj, boolean pauseImmediately) {
+            boolean oomAdj) {
         if (DEBUG_RESULTS || DEBUG_STATES) {
             Slog.v(TAG_STATES, "Finishing activity r=" + this + ", result=" + resultCode
                     + ", data=" + resultData + ", reason=" + reason);
@@ -1699,7 +1697,13 @@
             return FINISH_RESULT_CANCELLED;
         }
 
-        mAtmService.mWindowManager.deferSurfaceLayout();
+        final ActivityStack stack = getActivityStack();
+        final boolean mayAdjustFocus = (isState(RESUMED) || stack.mResumedActivity == null)
+                // It must be checked before {@link #makeFinishingLocked} is called, because a stack
+                // is not visible if it only contains finishing activities.
+                && mRootActivityContainer.isTopDisplayFocusedStack(stack);
+
+        mAtmService.deferWindowLayout();
         try {
             makeFinishingLocked();
             final TaskRecord task = getTaskRecord();
@@ -1720,8 +1724,12 @@
 
             pauseKeyDispatchingLocked();
 
-            final ActivityStack stack = getActivityStack();
-            stack.adjustFocusedActivityStack(this, "finishIfPossible");
+            // We are finishing the top focused activity and its stack has nothing to be focused so
+            // the next focusable stack should be focused.
+            if (mayAdjustFocus
+                    && (stack.topRunningActivityLocked() == null || !stack.isFocusable())) {
+                stack.adjustFocusToNextFocusableStack("finish-top");
+            }
 
             finishActivityResults(resultCode, resultData);
 
@@ -1758,7 +1766,8 @@
                     if (DEBUG_USER_LEAVING) {
                         Slog.v(TAG_USER_LEAVING, "finish() => pause with userLeaving=false");
                     }
-                    stack.startPausingLocked(false, false, null, pauseImmediately);
+                    stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+                            null /* resuming */);
                 }
 
                 if (endTask) {
@@ -1800,7 +1809,7 @@
 
             return FINISH_RESULT_REQUESTED;
         } finally {
-            mAtmService.mWindowManager.continueSurfaceLayout();
+            mAtmService.continueWindowLayout();
         }
     }
 
@@ -1844,9 +1853,14 @@
         final ActivityRecord next = getDisplay().topRunningActivity(
                 true /* considerKeyguardState */);
         final boolean isVisible = visible || nowVisible;
+        // isNextNotYetVisible is to check if the next activity is invisible, or it has been
+        // requested to be invisible but its windows haven't reported as invisible.  If so, it
+        // implied that the current finishing activity should be added into stopping list rather
+        // than destroy immediately.
+        final boolean isNextNotYetVisible = next != null && (!next.nowVisible || !next.visible);
         final ActivityStack stack = getActivityStack();
         final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack();
-        if (isVisible && next != null && !next.nowVisible) {
+        if (isVisible && isNextNotYetVisible) {
             addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
                     "completeFinishing");
             if (DEBUG_STATES) {
@@ -2094,7 +2108,7 @@
                 // TODO: If the callers to removeTask() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeTask()
                 mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
+                        !REMOVE_FROM_RECENTS, reason);
             }
 
             // We must keep the task around until all activities are destroyed. The following
@@ -2188,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();
+        }
     }
 
     /**
@@ -2533,6 +2549,8 @@
             return;
         }
         mAppWindowToken.setVisibility(visible, mDeferHidingClient);
+        mAtmService.addWindowLayoutReasons(
+                ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
         mStackSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
     }
 
@@ -2945,6 +2963,11 @@
         if (display != null) {
             display.handleActivitySizeCompatModeIfNeeded(r);
         }
+
+        if (r.mAppWindowToken != null) {
+            r.mAppWindowToken.getDisplayContent().mUnknownAppVisibilityController
+                    .notifyAppResumedFinished(r.mAppWindowToken);
+        }
     }
 
     /**
@@ -3012,7 +3035,6 @@
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this);
                     if (finishIfPossible("stop-no-history", false /* oomAdj */)
                             != FINISH_RESULT_CANCELLED) {
-                        // {@link adjustFocusedActivityStack} must have been already called.
                         resumeKeyDispatchingLocked();
                         return;
                     }
@@ -3028,7 +3050,6 @@
         if (!attachedToProcess()) {
             return;
         }
-        stack.adjustFocusedActivityStack(this, "stopActivity");
         resumeKeyDispatchingLocked();
         try {
             stopped = false;
@@ -4297,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 5bd557d..2ab3e01 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -33,6 +33,7 @@
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 import static android.view.Display.INVALID_DISPLAY;
@@ -63,7 +64,6 @@
 import static com.android.server.wm.ActivityStack.ActivityState.STARTED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.wm.ActivityStack.ActivityState.STOPPING;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
@@ -135,6 +135,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
@@ -636,11 +637,15 @@
             display.onStackWindowingModeChanged(this);
         }
         if (hasNewOverrideBounds) {
-            // Note the resizeStack may enter onConfigurationChanged recursively, so we make a copy
-            // of the temporary bounds (newBounds is mTmpRect) to avoid it being modified.
-            mRootActivityContainer.resizeStack(this, new Rect(newBounds), null /* tempTaskBounds */,
-                    null /* tempTaskInsetBounds */, PRESERVE_WINDOWS,
-                    true /* allowResizeInDockedMode */, true /* deferResume */);
+            if (inSplitScreenPrimaryWindowingMode()) {
+                mStackSupervisor.resizeDockedStackLocked(new Rect(newBounds),
+                        null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
+                        PRESERVE_WINDOWS, true /* deferResume */);
+            } else {
+                resize(new Rect(newBounds), null /* tempTaskBounds */,
+                        null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
+            }
         }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
             // Since always on top is only on when the stack is freeform or pinned, the state
@@ -752,7 +757,6 @@
             return;
         }
 
-        final WindowManagerService wm = mService.mWindowManager;
         final ActivityRecord topActivity = getTopActivity();
 
         // For now, assume that the Stack's windowing mode is what will actually be used
@@ -774,7 +778,7 @@
                     topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
         }
 
-        wm.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (!animate && topActivity != null) {
                 mStackSupervisor.mNoAnimActivities.add(topActivity);
@@ -819,7 +823,8 @@
             }
 
             if (!Objects.equals(getRequestedOverrideBounds(), mTmpRect2)) {
-                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        false /* preserveWindows */, true /* deferResume */);
             }
         } finally {
             if (showRecents && !alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
@@ -834,17 +839,18 @@
                 // so that the divider matches and remove this logic.
                 // TODO: This is currently only called when entering split-screen while in another
                 // task, and from the tests
-                // TODO (b/78247419): Check if launcher and overview are same then move home stack
-                // instead of recents stack. Then fix the rotation animation from fullscreen to
-                // minimized mode
+                // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode
+                final boolean isRecentsComponentHome =
+                        mService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser);
                 final ActivityStack recentStack = display.getOrCreateStack(
-                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS,
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS,
                         true /* onTop */);
                 recentStack.moveToFront("setWindowingMode");
                 // If task moved to docked stack - show recents if needed.
                 mService.mWindowManager.showRecentApps();
             }
-            wm.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (!deferEnsuringVisibility) {
@@ -1547,7 +1553,7 @@
             if (DEBUG_USER_LEAVING) Slog.v(TAG_USER_LEAVING,
                     "Sleep => pause with userLeaving=false");
 
-            startPausingLocked(false, true, null, false);
+            startPausingLocked(false /* userLeaving */, true /* uiSleeping */, null /* resuming */);
             shouldSleep = false ;
         } else if (mPausingActivity != null) {
             // Still waiting for something to pause; can't sleep yet.
@@ -1617,13 +1623,11 @@
      * @param resuming The activity we are currently trying to resume or null if this is not being
      *                 called as part of resuming the top activity, so we shouldn't try to instigate
      *                 a resume here if not null.
-     * @param pauseImmediately True if the caller does not want to wait for the activity callback to
-     *                         complete pausing.
      * @return Returns true if an activity now is in the PAUSING state, and we are waiting for
      * it to tell us when it is done.
      */
     final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
-            ActivityRecord resuming, boolean pauseImmediately) {
+            ActivityRecord resuming) {
         if (mPausingActivity != null) {
             Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity
                     + " state=" + mPausingActivity.getState());
@@ -1661,6 +1665,19 @@
 
         mService.updateCpuStats();
 
+        boolean pauseImmediately = false;
+        if (resuming != null && (resuming.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0) {
+            // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous
+            // activity to be paused, while at the same time resuming the new resume activity
+            // only if the previous activity can't go into Pip since we want to give Pip
+            // activities a chance to enter Pip before resuming the next activity.
+            final boolean lastResumedCanPip = prev != null && prev.checkEnterPictureInPictureState(
+                    "shouldResumeWhilePausing", userLeaving);
+            if (!lastResumedCanPip) {
+                pauseImmediately = true;
+            }
+        }
+
         if (prev.attachedToProcess()) {
             if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
             try {
@@ -1733,11 +1750,11 @@
             if (mPausingActivity == r) {
                 if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r
                         + (timeout ? " (due to timeout)" : " (pause complete)"));
-                mService.mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
                 } finally {
-                    mService.mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                 }
                 return;
             } else {
@@ -1758,7 +1775,8 @@
         mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
     }
 
-    private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
+    @VisibleForTesting
+    void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
         ActivityRecord prev = mPausingActivity;
         if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev);
 
@@ -2197,7 +2215,7 @@
      * Returns true if this stack should be resized to match the bounds specified by
      * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
      */
-    boolean resizeStackWithLaunchBounds() {
+    boolean shouldResizeStackWithLaunchBounds() {
         return inPinnedWindowingMode();
     }
 
@@ -2594,7 +2612,6 @@
 
         mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
 
-        boolean lastResumedCanPip = false;
         ActivityRecord lastResumed = null;
         final ActivityStack lastFocusedStack = display.getLastFocusedStack();
         if (lastFocusedStack != null && lastFocusedStack != this) {
@@ -2608,23 +2625,15 @@
                         + " next=" + next + " lastResumed=" + lastResumed);
                 userLeaving = false;
             }
-            lastResumedCanPip = lastResumed != null && lastResumed.checkEnterPictureInPictureState(
-                    "resumeTopActivity", userLeaving /* beforeStopping */);
         }
-        // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
-        // to be paused, while at the same time resuming the new resume activity only if the
-        // previous activity can't go into Pip since we want to give Pip activities a chance to
-        // enter Pip before resuming the next activity.
-        final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0
-                && !lastResumedCanPip;
 
-        boolean pausing = display.pauseBackStacks(userLeaving, next, false);
+        boolean pausing = display.pauseBackStacks(userLeaving, next);
         if (mResumedActivity != null) {
             if (DEBUG_STATES) Slog.d(TAG_STATES,
                     "resumeTopActivityLocked: Pausing " + mResumedActivity);
-            pausing |= startPausingLocked(userLeaving, false, next, false);
+            pausing |= startPausingLocked(userLeaving, false /* uiSleeping */, next);
         }
-        if (pausing && !resumeWhilePausing) {
+        if (pausing) {
             if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
                     "resumeTopActivityLocked: Skip resume: need to start pausing");
             // At this point we want to put the upcoming activity's process
@@ -3542,50 +3551,6 @@
         return taskTop;
     }
 
-    void adjustFocusedActivityStack(ActivityRecord r, String reason) {
-        if (!mRootActivityContainer.isTopDisplayFocusedStack(this) ||
-                ((mResumedActivity != r) && (mResumedActivity != null))) {
-            return;
-        }
-
-        final ActivityRecord next = topRunningActivityLocked();
-        final String myReason = reason + " adjustFocus";
-
-        if (next == r) {
-            final ActivityRecord top = mRootActivityContainer.topRunningActivity();
-            if (top != null) {
-                top.moveFocusableActivityToTop(myReason);
-            }
-            return;
-        }
-
-        if (next != null && isFocusable()) {
-            // Keep focus in stack if we have a top running activity and are focusable.
-            return;
-        }
-
-        // Task is not guaranteed to be non-null. For example, destroying the
-        // {@link ActivityRecord} will disassociate the task from the activity.
-        final TaskRecord task = r.getTaskRecord();
-
-        if (task == null) {
-            throw new IllegalStateException("activity no longer associated with task:" + r);
-        }
-
-        // Move focus to next focusable stack if possible.
-        final ActivityStack nextFocusableStack = adjustFocusToNextFocusableStack(myReason);
-        if (nextFocusableStack != null) {
-            final ActivityRecord top = nextFocusableStack.topRunningActivityLocked();
-            if (top != null && top == mRootActivityContainer.getTopResumedActivity()) {
-                mService.setResumedActivityUncheckLocked(top, reason);
-            }
-            return;
-        }
-
-        // Whatever...go home.
-        getDisplay().moveHomeActivityToTop(myReason);
-    }
-
     /**
      * Find next proper focusable stack and make it focused.
      * @return The stack that now got the focus, {@code null} if none found.
@@ -3617,6 +3582,14 @@
         }
 
         stack.moveToFront(myReason);
+        // Top display focused stack is changed, update top resumed activity if needed.
+        if (stack.mResumedActivity != null) {
+            mStackSupervisor.updateTopResumedActivityIfNeeded();
+            // Set focused app directly because if the next focused activity is already resumed
+            // (e.g. the next top activity is on a different display), there won't have activity
+            // state change to update it.
+            mService.setResumedActivityUncheckLocked(stack.mResumedActivity, reason);
+        }
         return stack;
     }
 
@@ -3838,8 +3811,7 @@
         final long origId = Binder.clearCallingIdentity();
         for (int i = start; i > finishTo; i--) {
             final ActivityRecord r = activities.get(i);
-            r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */,
-                    !PAUSE_IMMEDIATELY);
+            r.finishIfPossible(resultCode, resultData, "navigate-up", true /* oomAdj */);
             // Only return the supplied result for the first activity finished
             resultCode = Activity.RESULT_CANCELED;
             resultData = null;
@@ -3876,8 +3848,7 @@
                 } catch (RemoteException e) {
                     foundParentInTask = false;
                 }
-                parent.finishIfPossible(resultCode, resultData, "navigate-top",
-                        true /* oomAdj */, !PAUSE_IMMEDIATELY);
+                parent.finishIfPossible(resultCode, resultData, "navigate-top", true /* oomAdj */);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -4380,31 +4351,42 @@
         }
     }
 
-    // TODO: Figure-out a way to consolidate with resize() method below.
-    void requestResize(Rect bounds) {
-        mService.resizeStack(mStackId, bounds,
-                true /* allowResizeInDockedMode */, false /* preserveWindows */,
-                false /* animate */, -1 /* animationDuration */);
-    }
-
     // TODO: Can only be called from special methods in ActivityStackSupervisor.
     // Need to consolidate those calls points into this resize method so anyone can call directly.
-    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
+    void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
+            boolean preserveWindows, boolean deferResume) {
         if (!updateBoundsAllowed(bounds)) {
             return;
         }
 
-        // Update override configurations of all tasks in the stack.
-        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
-
-        for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
-            final TaskRecord task = mTaskHistory.get(i);
-            if (task.isResizeable()) {
-                task.updateOverrideConfiguration(taskBounds, tempTaskInsetBounds);
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
+        mService.deferWindowLayout();
+        try {
+            // Update override configurations of all tasks in the stack.
+            final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
+            for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+                final TaskRecord task = mTaskHistory.get(i);
+                if (task.isResizeable()) {
+                    if (tempTaskInsetBounds != null && !tempTaskInsetBounds.isEmpty()) {
+                        task.setDisplayedBounds(taskBounds);
+                        task.setBounds(tempTaskInsetBounds);
+                    } else {
+                        task.setDisplayedBounds(null);
+                        task.setBounds(taskBounds);
+                    }
+                }
             }
-        }
 
-        setBounds(bounds);
+            setBounds(bounds);
+
+            if (!deferResume) {
+                ensureVisibleActivitiesConfigurationLocked(
+                        topRunningActivityLocked(), preserveWindows);
+            }
+        } finally {
+            mService.continueWindowLayout();
+            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+        }
     }
 
     void onPipAnimationEndResize() {
@@ -4533,18 +4515,28 @@
      *         then skip running tasks that match those types.
      */
     void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
-            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) {
+            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed,
+            boolean crossUser, ArraySet<Integer> profileIds) {
         boolean focusedStack = mRootActivityContainer.getTopDisplayFocusedStack() == this;
         boolean topTask = true;
+        int userId = UserHandle.getUserId(callingUid);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.getTopActivity() == null) {
                 // Skip if there are no activities in the task
                 continue;
             }
-            if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) {
-                // Skip if the caller can't fetch this task
-                continue;
+            if (task.effectiveUid != callingUid) {
+                if (task.userId != userId && !crossUser && !profileIds.contains(task.userId)) {
+                    // Skip if the caller does not have cross user permission or cannot access
+                    // the task's profile
+                    continue;
+                }
+                if (!allowed && !task.isActivityTypeHome()) {
+                    // Skip if the caller isn't allowed to fetch this task, except for the home
+                    // task which we always return.
+                    continue;
+                }
             }
             if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
                     && task.getActivityType() == ignoreActivityType) {
@@ -4758,6 +4750,7 @@
             task.cleanUpResourcesForDestroy();
         }
 
+        final ActivityDisplay display = getDisplay();
         if (mTaskHistory.isEmpty()) {
             if (DEBUG_STACK) Slog.i(TAG_STACK, "removeTask: removing stack=" + this);
             // We only need to adjust focused stack if this stack is in focus and we are not in the
@@ -4766,11 +4759,11 @@
                     && mRootActivityContainer.isTopDisplayFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
                 if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) {
-                    getDisplay().moveHomeStackToFront(myReason);
+                    display.moveHomeStackToFront(myReason);
                 }
             }
             if (isAttached()) {
-                getDisplay().positionChildAtBottom(this);
+                display.positionChildAtBottom(this);
             }
             if (!isActivityTypeHome() || !isAttached()) {
                 remove();
@@ -4783,6 +4776,9 @@
         if (inPinnedWindowingMode()) {
             mService.getTaskChangeNotificationController().notifyActivityUnpinned();
         }
+        if (display.isSingleTaskInstance()) {
+            mService.notifySingleTaskDisplayEmpty(display.mDisplayId);
+        }
     }
 
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
@@ -4806,7 +4802,7 @@
         if (!mStackSupervisor.getLaunchParamsController()
                 .layoutTask(task, info.windowLayout, activity, source, options)
                 && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
-            task.updateOverrideConfiguration(getRequestedOverrideBounds());
+            task.setBounds(getRequestedOverrideBounds());
         }
         task.createTask(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
@@ -4945,12 +4941,6 @@
         }
     }
 
-
-    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-        if (getTaskStack() == null) return null;
-        return getTaskStack().getPictureInPictureBounds(aspectRatio, null /* currentStackBounds */);
-    }
-
     void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a06b9ce..d151f86 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -200,10 +200,6 @@
     // Used to indicate that a task is removed it should also be removed from recents.
     static final boolean REMOVE_FROM_RECENTS = true;
 
-    // Used to indicate that pausing an activity should occur immediately without waiting for
-    // the activity callback indicating that it has completed pausing
-    static final boolean PAUSE_IMMEDIATELY = true;
-
     /** True if the docked stack is currently being resized. */
     private boolean mDockedStackResizing;
 
@@ -1402,7 +1398,7 @@
         boolean reparented = false;
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
             final Rect bounds = options.getLaunchBounds();
-            task.updateOverrideConfiguration(bounds);
+            task.setBounds(bounds);
 
             ActivityStack stack =
                     mRootActivityContainer.getLaunchStack(null, options, task, ON_TOP);
@@ -1416,10 +1412,9 @@
                 // task.reparent() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
             }
-            if (stack.resizeStackWithLaunchBounds()) {
-                mRootActivityContainer.resizeStack(stack, bounds, null /* tempTaskBounds */,
-                        null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
-                        true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            if (stack.shouldResizeStackWithLaunchBounds()) {
+                stack.resize(bounds, null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                        !PRESERVE_WINDOWS, !DEFER_RESUME);
             } else {
                 // WM resizeTask must be done after the task is moved to the correct stack,
                 // because Task's setBounds() also updates dim layer's bounds, but that has
@@ -1500,7 +1495,7 @@
     private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
             int toDisplayId, boolean onTop) {
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             final int windowingMode = fromStack.getWindowingMode();
             final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
@@ -1566,7 +1561,7 @@
             mRootActivityContainer.resumeFocusedStacksTopActivities();
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -1635,12 +1630,13 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             // Don't allow re-entry while resizing. E.g. due to docked stack detaching.
             mAllowDockedStackResize = false;
             ActivityRecord r = stack.topRunningActivityLocked();
-            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds);
+            stack.resize(dockedBounds, tempDockedTaskBounds, tempDockedTaskInsetBounds,
+                    !PRESERVE_WINDOWS, DEFER_RESUME);
 
             // TODO: Checking for isAttached might not be needed as if the user passes in null
             // dockedBounds then they want the docked stack to be dismissed.
@@ -1678,11 +1674,19 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */);
 
-                    mRootActivityContainer.resizeStack(current,
-                            !tempRect.isEmpty() ? tempRect : null,
+                    if (tempRect.isEmpty()) {
+                        // If this scenario is hit, it means something is not working right.
+                        // Empty/null bounds implies fullscreen. In the event that this stack
+                        // *should* be fullscreen, its mode should be set explicitly in a form
+                        // of setWindowingMode so that other parts of the system are updated
+                        // properly.
+                        throw new IllegalArgumentException("Trying to set null bounds on a"
+                                + " non-fullscreen stack");
+                    }
+
+                    current.resize(tempRect,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
-                            tempOtherTaskInsetBounds, preserveWindows,
-                            true /* allowResizeInDockedMode */, deferResume);
+                            tempOtherTaskInsetBounds, preserveWindows, deferResume);
                 }
             }
             if (!deferResume) {
@@ -1690,7 +1694,7 @@
             }
         } finally {
             mAllowDockedStackResize = true;
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1714,9 +1718,8 @@
         }
 
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
-            ActivityRecord r = stack.topRunningActivityLocked();
             Rect insetBounds = null;
             if (tempPinnedTaskBounds != null && stack.isAnimatingBoundsToFullscreen()) {
                 // Use 0,0 as the position for the inset rect because we are headed for fullscreen.
@@ -1732,10 +1735,10 @@
                 // transitioning it from fullscreen into a floating state.
                 stack.onPipAnimationEndResize();
             }
-            stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds);
-            stack.ensureVisibleActivitiesConfigurationLocked(r, false);
+            stack.resize(pinnedBounds, tempPinnedTaskBounds, insetBounds, !PRESERVE_WINDOWS,
+                    !DEFER_RESUME);
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -1778,30 +1781,19 @@
     }
 
     /**
-     * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
-     */
-    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            String reason) {
-        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
-                reason);
-    }
-
-    /**
      * Removes the task with the specified task id.
      *
      * @param taskId Identifier of the task to be removed.
      * @param killProcess Kill any process associated with the task if possible.
      * @param removeFromRecents Whether to also remove the task from recents.
-     * @param pauseImmediately Pauses all task activities immediately without waiting for the
-     *                         pause-complete callback from the activity.
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            boolean pauseImmediately, String reason) {
+            String reason) {
         final TaskRecord tr =
                 mRootActivityContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
-            tr.removeTaskActivitiesLocked(pauseImmediately, reason);
+            tr.removeTaskActivitiesLocked(reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             mService.getLockTaskController().clearLockedTask(tr);
             if (tr.isPersistable) {
@@ -1925,7 +1917,7 @@
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
             removeTaskByIdLocked(task.taskId, killProcess, false /* removeFromRecents */,
-                    !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+                    "recent-task-trimmed");
         }
         task.removedFromRecents();
     }
@@ -2488,19 +2480,17 @@
     }
 
     void activityRelaunchedLocked(IBinder token) {
-        mWindowManager.notifyAppRelaunchingFinished(token);
         final ActivityRecord r = ActivityRecord.isInStackLocked(token);
         if (r != null) {
+            if (r.mAppWindowToken != null) {
+                r.mAppWindowToken.finishRelaunching();
+            }
             if (r.getActivityStack().shouldSleepOrShutDownActivities()) {
                 r.setSleeping(true, true);
             }
         }
     }
 
-    void activityRelaunchingLocked(ActivityRecord r) {
-        mWindowManager.notifyAppRelaunching(r.appToken);
-    }
-
     void logStackState() {
         mActivityMetricsLogger.logWindowState();
     }
@@ -2738,7 +2728,7 @@
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateStateLocked(
@@ -2829,7 +2819,7 @@
                     mWindowManager.checkSplitScreenMinimizedChanged(false /* animate */);
                 }
             }
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 48bc963..47be792 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -81,7 +81,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
-import static com.android.server.wm.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
 
 import android.annotation.NonNull;
@@ -1401,7 +1400,7 @@
         int result = START_CANCELED;
         final ActivityStack startedActivityStack;
         try {
-            mService.mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                     startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
         } finally {
@@ -1437,7 +1436,7 @@
                     startedActivityStack.remove();
                 }
             }
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         postStartActivityProcessing(r, result, startedActivityStack);
@@ -1502,205 +1501,60 @@
             mSupervisor.mRecentTasks.setFreezeTaskListReordering();
         }
 
-        // Do not start home activity if it cannot be launched on preferred display. We are not
-        // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
-        // fallback to launch on other displays.
-        if (r.isActivityTypeHome() && !mRootActivityContainer.canStartHomeOnDisplay(r.info,
-                mPreferredDisplayId, true /* allowInstrumenting */)) {
-            Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId);
-            return START_CANCELED;
+        // Compute if there is an existing task that should be used for.
+        final TaskRecord targetTask = computeTargetTask(reusedActivity);
+        final boolean newTask = targetTask == null;
+
+        // Check if starting activity on given task or on a new task is allowed.
+        int startResult = isAllowedToStart(r, newTask, targetTask);
+        if (startResult != START_SUCCESS) {
+            return startResult;
         }
 
-        if (reusedActivity != null) {
-            // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
-            // still needs to be a lock task mode violation since the task gets cleared out and
-            // the device would otherwise leave the locked task.
-            if (mService.getLockTaskController().isLockTaskModeViolation(
-                    reusedActivity.getTaskRecord(),
-                    (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
-                Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
-                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+        final ActivityRecord targetTaskTop = newTask ? null : targetTask.getTopActivity();
+        if (targetTaskTop != null) {
+            // Recycle the target task for this launch.
+            startResult = recycleTask(targetTask, targetTaskTop, reusedActivity, outActivity);
+            if (startResult != START_SUCCESS) {
+                return startResult;
             }
-
-            // True if we are clearing top and resetting of a standard (default) launch mode
-            // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
-            final boolean clearTopAndResetStandardLaunchMode =
-                    (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
-                            == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-                    && mLaunchMode == LAUNCH_MULTIPLE;
-
-            // If mStartActivity does not have a task associated with it, associate it with the
-            // reused activity's task. Do not do so if we're clearing top and resetting for a
-            // standard launchMode activity.
-            if (mStartActivity.getTaskRecord() == null && !clearTopAndResetStandardLaunchMode) {
-                mStartActivity.setTask(reusedActivity.getTaskRecord());
-            }
-
-            if (reusedActivity.getTaskRecord().intent == null) {
-                // This task was started because of movement of the activity based on affinity...
-                // Now that we are actually launching it, we can assign the base intent.
-                reusedActivity.getTaskRecord().setIntent(mStartActivity);
-            } else {
-                final boolean taskOnHome =
-                        (mStartActivity.intent.getFlags() & FLAG_ACTIVITY_TASK_ON_HOME) != 0;
-                if (taskOnHome) {
-                    reusedActivity.getTaskRecord().intent.addFlags(FLAG_ACTIVITY_TASK_ON_HOME);
-                } else {
-                    reusedActivity.getTaskRecord().intent.removeFlags(FLAG_ACTIVITY_TASK_ON_HOME);
-                }
-            }
-
-            // This code path leads to delivering a new intent, we want to make sure we schedule it
-            // as the first operation, in case the activity will be resumed as a result of later
-            // operations.
-            if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                    || isDocumentLaunchesIntoExisting(mLaunchFlags)
-                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
-                final TaskRecord task = reusedActivity.getTaskRecord();
-
-                // In this situation we want to remove all activities from the task up to the one
-                // being started. In most cases this means we are resetting the task to its initial
-                // state.
-                final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
-                        mLaunchFlags);
-
-                // The above code can remove {@code reusedActivity} from the task, leading to the
-                // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
-                // task reference is needed in the call below to
-                // {@link setTargetStackAndMoveToFrontIfNeeded}.
-                if (reusedActivity.getTaskRecord() == null) {
-                    reusedActivity.setTask(task);
-                }
-
-                if (top != null) {
-                    if (top.isRootOfTask()) {
-                        // Activity aliases may mean we use different intents for the top activity,
-                        // so make sure the task now has the identity of the new intent.
-                        top.getTaskRecord().setIntent(mStartActivity);
-                    }
-                    deliverNewIntent(top);
-                }
-            }
-
-            mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded
-                    (false /* forceSend */, reusedActivity);
-
-            reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
-
-            final ActivityRecord outResult =
-                    outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
-
-            // When there is a reused activity and the current result is a trampoline activity,
-            // set the reused activity as the result.
-            if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
-                outActivity[0] = reusedActivity;
-            }
-
-            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                // We don't need to start a new activity, and the client said not to do anything
-                // if that is the case, so this is it!  And for paranoia, make sure we have
-                // correctly resumed the top activity.
-                resumeTargetStackIfNeeded();
-                return START_RETURN_INTENT_TO_CALLER;
-            }
-
-            if (reusedActivity != null) {
-                setTaskFromIntentActivity(reusedActivity);
-
-                if (!mAddingToTask && mReuseTask == null) {
-                    // We didn't do anything...  but it was needed (a.k.a., client don't use that
-                    // intent!)  And for paranoia, make sure we have correctly resumed the top activity.
-                    resumeTargetStackIfNeeded();
-                    if (outActivity != null && outActivity.length > 0) {
-                        // The reusedActivity could be finishing, for example of starting an
-                        // activity with FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the
-                        // top running activity in the task instead.
-                        outActivity[0] = reusedActivity.finishing
-                                ? reusedActivity.getTaskRecord().getTopActivity() : reusedActivity;
-                    }
-
-                    return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
-                }
-            }
-        }
-
-        if (mStartActivity.packageName == null) {
-            if (mStartActivity.resultTo != null) {
-                mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
-                        mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
-            }
-            ActivityOptions.abort(mOptions);
-            return START_CLASS_NOT_FOUND;
         }
 
         // If the activity being launched is the same as the one currently at the top, then
         // we need to check if it should only be launched once.
         final ActivityStack topStack = mRootActivityContainer.getTopDisplayFocusedStack();
-        final ActivityRecord topFocused = topStack.getTopActivity();
-        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
-        final boolean dontStart = top != null && mStartActivity.resultTo == null
-                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
-                && top.mUserId == mStartActivity.mUserId
-                && top.attachedToProcess()
-                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
-                // This allows home activity to automatically launch on secondary display when
-                // display added, if home was the top activity on default display, instead of
-                // sending new intent to the home activity on default display.
-                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
-        if (dontStart) {
-            // For paranoia, make sure we have correctly resumed the top activity.
-            topStack.mLastPausedActivity = null;
-            if (mDoResume) {
-                mRootActivityContainer.resumeFocusedStacksTopActivities();
-            }
-            ActivityOptions.abort(mOptions);
-            if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                // We don't need to start a new activity, and the client said not to do
-                // anything if that is the case, so this is it!
-                return START_RETURN_INTENT_TO_CALLER;
-            }
-
-            deliverNewIntent(top);
-
-            // Don't use mStartActivity.task to show the toast. We're not starting a new activity
-            // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
-            mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(), preferredWindowingMode,
-                    mPreferredDisplayId, topStack);
-
-            return START_DELIVERED_TO_TOP;
+        startResult = deliverToCurrentTopIfNeeded(topStack);
+        if (startResult != START_SUCCESS) {
+            return startResult;
         }
 
-        boolean newTask = false;
-        final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
-                ? mSourceRecord.getTaskRecord() : null;
-
-        // Should this be considered a new task?
-        int result = START_SUCCESS;
-        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
-                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
-            newTask = true;
-            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
-        } else if (mSourceRecord != null) {
-            result = setTaskFromSourceRecord();
-        } else if (mInTask != null) {
-            result = setTaskFromInTask();
-        } else {
-            // This not being started from an existing activity, and not part of a new task...
-            // just put it in the top task, though these days this case should never happen.
-            result = setTaskToCurrentTopOrCreateNewTask();
+        if (mTargetStack == null) {
+            mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
         }
-        if (result != START_SUCCESS) {
-            return result;
+        if (newTask) {
+            final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
+                    ? mSourceRecord.getTaskRecord() : null;
+            setNewTask(taskToAffiliate);
+            if (mService.getLockTaskController().isLockTaskModeViolation(
+                    mStartActivity.getTaskRecord())) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+        } else if (mAddingToTask) {
+            addOrReparentStartingActivity(targetTask, "adding to task");
+        }
+
+        if (!mAvoidMoveToFront && mDoResume) {
+            mTargetStack.moveToFront("reuseOrNewTask");
         }
 
         mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
-        mService.getPackageManagerInternalLocked().grantEphemeralAccess(
+        mService.getPackageManagerInternalLocked().grantImplicitAccess(
                 mStartActivity.mUserId, mIntent,
-                UserHandle.getAppId(mStartActivity.info.applicationInfo.uid),
-                UserHandle.getAppId(mCallingUid));
+                UserHandle.getAppId(mCallingUid),
+                UserHandle.getAppId(mStartActivity.info.applicationInfo.uid)
+        );
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
                     mStartActivity.getTaskRecord().taskId);
@@ -1712,8 +1566,8 @@
         mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
                 false /* forceSend */, mStartActivity);
 
-        mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
-                mOptions);
+        mTargetStack.startActivityLocked(mStartActivity, topStack.getTopActivity(), newTask,
+                mKeepCurTransition, mOptions);
         if (mDoResume) {
             final ActivityRecord topTaskActivity =
                     mStartActivity.getTaskRecord().topRunningActivityLocked();
@@ -1754,6 +1608,312 @@
         return START_SUCCESS;
     }
 
+    private TaskRecord computeTargetTask(ActivityRecord reusedActivity) {
+        if (reusedActivity != null) {
+            return reusedActivity.getTaskRecord();
+        } else if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
+                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
+            // A new task should be created instead of using existing one.
+            return null;
+        } else if (mSourceRecord != null) {
+            return mSourceRecord.getTaskRecord();
+        } else if (mInTask != null) {
+            return mInTask;
+        } else {
+            final ActivityRecord top = computeStackFocus(mStartActivity, false /* newTask */,
+                    mLaunchFlags, mOptions).getTopActivity();
+            if (top != null) {
+                return top.getTaskRecord();
+            }
+        }
+        return null;
+    }
+
+    private int isAllowedToStart(ActivityRecord r, boolean newTask, TaskRecord targetTask) {
+        if (mStartActivity.packageName == null) {
+            if (mStartActivity.resultTo != null) {
+                mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+                        mStartActivity.requestCode, RESULT_CANCELED, null /* data */);
+            }
+            ActivityOptions.abort(mOptions);
+            return START_CLASS_NOT_FOUND;
+        }
+
+        // Do not start home activity if it cannot be launched on preferred display. We are not
+        // doing this in ActivityStackSupervisor#canPlaceEntityOnDisplay because it might
+        // fallback to launch on other displays.
+        if (r.isActivityTypeHome() && !mRootActivityContainer.canStartHomeOnDisplay(r.info,
+                mPreferredDisplayId, true /* allowInstrumenting */)) {
+            Slog.w(TAG, "Cannot launch home on display " + mPreferredDisplayId);
+            return START_CANCELED;
+        }
+
+        if (mRestrictedBgActivity && (newTask || !targetTask.containsAppUid(mCallingUid))
+                && handleBackgroundActivityAbort(mStartActivity)) {
+            Slog.e(TAG, "Abort background activity starts from " + mCallingUid);
+            return START_ABORTED;
+        }
+
+        // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but still
+        // needs to be a lock task mode violation since the task gets cleared out and the device
+        // would otherwise leave the locked task.
+        final boolean isNewClearTask =
+                (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                        == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
+        if (!newTask && mService.getLockTaskController().isLockTaskModeViolation(targetTask,
+                isNewClearTask)) {
+            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+        }
+
+        return START_SUCCESS;
+    }
+
+    /**
+     * Prepare the target task to be reused for this launch, which including:
+     * - Position the target task on valid stack on preferred display.
+     * - Comply to the specified activity launch flags
+     * - Determine whether need to add a new activity on top or just brought the task to front.
+     */
+    private int recycleTask(TaskRecord targetTask, ActivityRecord targetTaskTop,
+            ActivityRecord reusedActivity, ActivityRecord[] outActivity) {
+        // True if we are clearing top and resetting of a standard (default) launch mode
+        // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished.
+        final boolean clearTopAndResetStandardLaunchMode =
+                (mLaunchFlags & (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED))
+                        == (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                        && mLaunchMode == LAUNCH_MULTIPLE;
+
+        // If mStartActivity does not have a task associated with it, associate it with the
+        // reused activity's task. Do not do so if we're clearing top and resetting for a
+        // standard launchMode activity.
+        if (mStartActivity.getTaskRecord() == null && !clearTopAndResetStandardLaunchMode) {
+            mStartActivity.setTask(targetTask);
+        }
+
+        if (reusedActivity != null) {
+            if (targetTask.intent == null) {
+                // This task was started because of movement of the activity based on
+                // affinity...
+                // Now that we are actually launching it, we can assign the base intent.
+                targetTask.setIntent(mStartActivity);
+            } else {
+                final boolean taskOnHome =
+                        (mStartActivity.intent.getFlags() & FLAG_ACTIVITY_TASK_ON_HOME) != 0;
+                if (taskOnHome) {
+                    targetTask.intent.addFlags(FLAG_ACTIVITY_TASK_ON_HOME);
+                } else {
+                    targetTask.intent.removeFlags(FLAG_ACTIVITY_TASK_ON_HOME);
+                }
+            }
+        }
+
+        mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(false /* forceSend */,
+                targetTaskTop);
+
+        setTargetStackIfNeeded(targetTaskTop);
+
+        final ActivityRecord outResult =
+                outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
+
+        // When there is a reused activity and the current result is a trampoline activity,
+        // set the reused activity as the result.
+        if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
+            outActivity[0] = targetTaskTop;
+        }
+
+        if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
+            // We don't need to start a new activity, and the client said not to do anything
+            // if that is the case, so this is it!  And for paranoia, make sure we have
+            // correctly resumed the top activity.
+            if (!mMovedToFront && mDoResume) {
+                if (DEBUG_TASKS) {
+                    Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
+                            + " from " + targetTaskTop);
+                }
+                mTargetStack.moveToFront("intentActivityFound");
+            }
+            resumeTargetStackIfNeeded();
+            return START_RETURN_INTENT_TO_CALLER;
+        }
+
+        complyActivityFlags(targetTask, reusedActivity);
+
+        if (mAddingToTask) {
+            return START_SUCCESS;
+        }
+
+        if (!mMovedToFront && mDoResume) {
+            mTargetStack.moveToFront("intentActivityFound");
+        }
+        // We didn't do anything...  but it was needed (a.k.a., client don't use that intent!)
+        // And for paranoia, make sure we have correctly resumed the top activity.
+        resumeTargetStackIfNeeded();
+        if (outActivity != null && outActivity.length > 0) {
+            // The reusedActivity could be finishing, for example of starting an activity with
+            // FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the top running activity in the
+            // task instead.
+            outActivity[0] = targetTaskTop.finishing ? targetTask.getTopActivity() : targetTaskTop;
+        }
+        return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
+    }
+
+    /**
+     * Check if the activity being launched is the same as the one currently at the top and it
+     * should only be launched once.
+     */
+    private int deliverToCurrentTopIfNeeded(ActivityStack topStack) {
+        final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
+        final boolean dontStart = top != null && mStartActivity.resultTo == null
+                && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
+                && top.mUserId == mStartActivity.mUserId
+                && top.attachedToProcess()
+                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
+                // This allows home activity to automatically launch on secondary display when
+                // display added, if home was the top activity on default display, instead of
+                // sending new intent to the home activity on default display.
+                && (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
+        if (!dontStart) {
+            return START_SUCCESS;
+        }
+
+        // For paranoia, make sure we have correctly resumed the top activity.
+        topStack.mLastPausedActivity = null;
+        if (mDoResume) {
+            mRootActivityContainer.resumeFocusedStacksTopActivities();
+        }
+        ActivityOptions.abort(mOptions);
+        if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
+            // We don't need to start a new activity, and the client said not to do anything if
+            // that is the case, so this is it!
+            return START_RETURN_INTENT_TO_CALLER;
+        }
+
+        deliverNewIntent(top);
+
+        // Don't use mStartActivity.task to show the toast. We're not starting a new activity but
+        // reusing 'top'. Fields in mStartActivity may not be fully initialized.
+        mSupervisor.handleNonResizableTaskIfNeeded(top.getTaskRecord(),
+                mLaunchParams.mWindowingMode, mPreferredDisplayId, topStack);
+
+        return START_DELIVERED_TO_TOP;
+    }
+
+    /**
+     * Applying the launching flags to the task, which might clear few or all the activities in the
+     * task.
+     */
+    private void complyActivityFlags(TaskRecord targetTask, ActivityRecord reusedActivity) {
+        ActivityRecord targetTaskTop = targetTask.getTopActivity();
+        if (reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+            targetTaskTop = mTargetStack.resetTaskIfNeededLocked(targetTaskTop, mStartActivity);
+        }
+
+        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
+            // The caller has requested to completely replace any existing task with its new
+            // activity. Well that should not be too hard...
+            // Note: we must persist the {@link TaskRecord} first as intentActivity could be
+            // removed from calling performClearTaskLocked (For example, if it is being brought out
+            // of history or if it is finished immediately), thus disassociating the task. Also note
+            // that mReuseTask is reset as a result of {@link TaskRecord#performClearTaskLocked}
+            // launching another activity.
+            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
+            // already launching one.
+            targetTask.performClearTaskLocked();
+            targetTask.setIntent(mStartActivity);
+            mAddingToTask = true;
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
+                || isDocumentLaunchesIntoExisting(mLaunchFlags)
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
+            // In this situation we want to remove all activities from the task up to the one
+            // being started. In most cases this means we are resetting the task to its initial
+            // state.
+            final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity,
+                    mLaunchFlags);
+
+            // The above code can remove {@code reusedActivity} from the task, leading to the
+            // the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
+            // task reference is needed in the call below to
+            // {@link setTargetStackAndMoveToFrontIfNeeded}.
+            if (targetTaskTop.getTaskRecord() == null) {
+                targetTaskTop.setTask(targetTask);
+            }
+
+            if (top != null) {
+                if (top.isRootOfTask()) {
+                    // Activity aliases may mean we use different intents for the top activity,
+                    // so make sure the task now has the identity of the new intent.
+                    top.getTaskRecord().setIntent(mStartActivity);
+                }
+                deliverNewIntent(top);
+            } else {
+                // A special case: we need to start the activity because it is not currently
+                // running, and the caller has asked to clear the current task to have this
+                // activity at the top.
+                mAddingToTask = true;
+                if (targetTask.getStack() == null) {
+                    // Target stack got cleared when we all activities were removed above.
+                    // Go ahead and reset it.
+                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
+                            mLaunchFlags, mOptions);
+                    mTargetStack.addTask(targetTask,
+                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
+                }
+            }
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) == 0 && !mAddingToTask
+                && (mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
+            // In this case, we are launching an activity in our own task that may
+            // already be running somewhere in the history, and we want to shuffle it to
+            // the front of the stack if so.
+            final ActivityRecord act = targetTask.findActivityInHistoryLocked(
+                    mStartActivity);
+            if (act != null) {
+                final TaskRecord task = act.getTaskRecord();
+                task.moveActivityToFrontLocked(act);
+                act.updateOptionsLocked(mOptions);
+                deliverNewIntent(act);
+                mTargetStack.mLastPausedActivity = null;
+            } else {
+                mAddingToTask = true;
+            }
+        } else if (mStartActivity.mActivityComponent.equals(targetTask.realActivity)) {
+            // In this case the top activity on the task is the same as the one being launched,
+            // so we take that as a request to bring the task to the foreground. If the top
+            // activity in the task is the root activity, deliver this new intent to it if it
+            // desires.
+            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                    || LAUNCH_SINGLE_TOP == mLaunchMode)
+                    && targetTaskTop.mActivityComponent.equals(
+                    mStartActivity.mActivityComponent) && mStartActivity.resultTo == null) {
+                if (targetTaskTop.isRootOfTask()) {
+                    targetTaskTop.getTaskRecord().setIntent(mStartActivity);
+                }
+                deliverNewIntent(targetTaskTop);
+            } else if (!targetTask.isSameIntentFilter(mStartActivity)) {
+                // In this case we are launching the root activity of the task, but with a
+                // different intent. We should start a new instance on top.
+                mAddingToTask = true;
+            } else if (reusedActivity == null) {
+                mAddingToTask = true;
+            }
+        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+            // In this case an activity is being launched in to an existing task, without
+            // resetting that task. This is typically the situation of launching an activity
+            // from a notification or shortcut. We want to place the new activity on top of the
+            // current task.
+            mAddingToTask = true;
+        } else if (!targetTask.rootWasReset) {
+            // In this case we are launching into an existing task that has not yet been started
+            // from its front door. The current task has been brought to the front. Ideally,
+            // we'd probably like to place this new task at the bottom of its stack, but that's
+            // a little hard to do with the current organization of the code so for now we'll
+            // just drop it.
+            targetTask.setIntent(mStartActivity);
+        }
+    }
+
     /**
      * Resets the {@link ActivityStarter} state.
      * @param clearRequest whether the request should be reset to default values.
@@ -2106,7 +2266,7 @@
      * @param intentActivity Existing matching activity.
      * @return {@link ActivityRecord} brought to front.
      */
-    private ActivityRecord setTargetStackAndMoveToFrontIfNeeded(ActivityRecord intentActivity) {
+    private void setTargetStackIfNeeded(ActivityRecord intentActivity) {
         mTargetStack = intentActivity.getActivityStack();
         mTargetStack.mLastPausedActivity = null;
         // If the target task is not in the front, then we need to bring it to the front...
@@ -2136,167 +2296,71 @@
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTaskRecord());
                 }
 
-                // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
-                // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
-                // So no point resuming any of the activities here, it just wastes one extra
-                // resuming, plus enter AND exit transitions.
-                // Here we only want to bring the target stack forward. Transition will be applied
-                // to the new activity that's started after the old ones are gone.
-                final boolean willClearTask =
-                        (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                            == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-                if (!willClearTask) {
-                    final ActivityStack launchStack = getLaunchStack(
-                            mStartActivity, mLaunchFlags, mStartActivity.getTaskRecord(), mOptions);
-                    final TaskRecord intentTask = intentActivity.getTaskRecord();
-                    if (launchStack == null || launchStack == mTargetStack) {
-                        // We only want to move to the front, if we aren't going to launch on a
-                        // different stack. If we launch on a different stack, we will put the
-                        // task on top there.
-                        mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
-                                mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
-                        mMovedToFront = true;
-                    } else if (launchStack.inSplitScreenWindowingMode()) {
-                        if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
-                            // If we want to launch adjacent and mTargetStack is not the computed
-                            // launch stack - move task to top of computed stack.
-                            intentTask.reparent(launchStack, ON_TOP,
-                                    REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                    "launchToSide");
-                        } else {
-                            // TODO: This should be reevaluated in MW v2.
-                            // We choose to move task to front instead of launching it adjacent
-                            // when specific stack was requested explicitly and it appeared to be
-                            // adjacent stack, but FLAG_ACTIVITY_LAUNCH_ADJACENT was not set.
-                            mTargetStack.moveTaskToFrontLocked(intentTask,
-                                    mNoAnimation, mOptions, mStartActivity.appTimeTracker,
-                                    "bringToFrontInsteadOfAdjacentLaunch");
-                        }
-                        mMovedToFront = launchStack != launchStack.getDisplay()
-                                .getTopStackInWindowingMode(launchStack.getWindowingMode());
-                    } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
-                        // Target and computed stacks are on different displays and we've
-                        // found a matching task - move the existing instance to that display and
-                        // move it to front.
-                        intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                final ActivityStack launchStack = getLaunchStack(
+                        mStartActivity, mLaunchFlags, mStartActivity.getTaskRecord(), mOptions);
+                final TaskRecord intentTask = intentActivity.getTaskRecord();
+                if (launchStack == null || launchStack == mTargetStack) {
+                    // We only want to move to the front, if we aren't going to launch on a
+                    // different stack. If we launch on a different stack, we will put the
+                    // task on top there.
+                    mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
+                            mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
+                    mMovedToFront = true;
+                } else if (launchStack.inSplitScreenWindowingMode()) {
+                    if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
+                        // If we want to launch adjacent and mTargetStack is not the computed
+                        // launch stack - move task to top of computed stack.
+                        intentTask.reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                "reparentToDisplay");
-                        mMovedToFront = true;
-                    } else if (launchStack.isActivityTypeHome()
-                            && !mTargetStack.isActivityTypeHome()) {
-                        // It is possible for the home activity to be in another stack initially.
-                        // For example, the activity may have been initially started with an intent
-                        // which placed it in the fullscreen stack. To ensure the proper handling of
-                        // the activity based on home stack assumptions, we must move it over.
-                        intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
-                                REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                                "reparentingHome");
-                        mMovedToFront = true;
+                                "launchToSide");
+                    } else {
+                        // TODO: This should be reevaluated in MW v2.
+                        // We choose to move task to front instead of launching it adjacent
+                        // when specific stack was requested explicitly and it appeared to be
+                        // adjacent stack, but FLAG_ACTIVITY_LAUNCH_ADJACENT was not set.
+                        mTargetStack.moveTaskToFrontLocked(intentTask,
+                                mNoAnimation, mOptions, mStartActivity.appTimeTracker,
+                                "bringToFrontInsteadOfAdjacentLaunch");
                     }
-                    mOptions = null;
-
-                    // We are moving a task to the front, use starting window to hide initial drawn
-                    // delay.
-                    intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
-                            true /* taskSwitch */);
+                    mMovedToFront = launchStack != launchStack.getDisplay()
+                            .getTopStackInWindowingMode(launchStack.getWindowingMode());
+                } else if (launchStack.mDisplayId != mTargetStack.mDisplayId) {
+                    // Target and computed stacks are on different displays and we've
+                    // found a matching task - move the existing instance to that display and
+                    // move it to front.
+                    intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                            REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                            "reparentToDisplay");
+                    mMovedToFront = true;
+                } else if (launchStack.isActivityTypeHome()
+                        && !mTargetStack.isActivityTypeHome()) {
+                    // It is possible for the home activity to be in another stack initially.
+                    // For example, the activity may have been initially started with an intent
+                    // which placed it in the fullscreen stack. To ensure the proper handling of
+                    // the activity based on home stack assumptions, we must move it over.
+                    intentActivity.getTaskRecord().reparent(launchStack, ON_TOP,
+                            REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                            "reparentingHome");
+                    mMovedToFront = true;
+                } else if (launchStack.topTask() == null) {
+                    // The task does not need to be reparented to the launch stack. Remove the
+                    // launch stack if there is no activity in it.
+                    launchStack.remove();
                 }
+
+                mOptions = null;
+
+                // We are moving a task to the front, use starting window to hide initial drawn
+                // delay.
+                intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
+                        true /* taskSwitch */);
             }
         }
         // Need to update mTargetStack because if task was moved out of it, the original stack may
         // be destroyed.
         mTargetStack = intentActivity.getActivityStack();
-        if (!mMovedToFront && mDoResume) {
-            if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + mTargetStack
-                    + " from " + intentActivity);
-            mTargetStack.moveToFront("intentActivityFound");
-        }
-
         mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTaskRecord(),
                 WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
-
-        // If the caller has requested that the target task be reset, then do so.
-        if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
-            return mTargetStack.resetTaskIfNeededLocked(intentActivity, mStartActivity);
-        }
-        return intentActivity;
-    }
-
-    private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
-        if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
-            // The caller has requested to completely replace any existing task with its new
-            // activity. Well that should not be too hard...
-            // Note: we must persist the {@link TaskRecord} first as intentActivity could be
-            // removed from calling performClearTaskLocked (For example, if it is being brought out
-            // of history or if it is finished immediately), thus disassociating the task. Also note
-            // that mReuseTask is reset as a result of {@link TaskRecord#performClearTaskLocked}
-            // launching another activity.
-            // TODO(b/36119896):  We shouldn't trigger activity launches in this path since we are
-            // already launching one.
-            final TaskRecord task = intentActivity.getTaskRecord();
-            task.performClearTaskLocked();
-            mReuseTask = task;
-            mReuseTask.setIntent(mStartActivity);
-        } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
-            final ActivityRecord top = intentActivity.getTaskRecord().performClearTaskLocked(
-                    mStartActivity, mLaunchFlags);
-            if (top == null) {
-                // A special case: we need to start the activity because it is not currently
-                // running, and the caller has asked to clear the current task to have this
-                // activity at the top.
-                mAddingToTask = true;
-
-                // We are no longer placing the activity in the task we previously thought we were.
-                mStartActivity.setTask(null);
-                // Now pretend like this activity is being started by the top of its task, so it
-                // is put in the right place.
-                mSourceRecord = intentActivity;
-                final TaskRecord task = mSourceRecord.getTaskRecord();
-                if (task != null && task.getStack() == null) {
-                    // Target stack got cleared when we all activities were removed above.
-                    // Go ahead and reset it.
-                    mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
-                            mLaunchFlags, mOptions);
-                    mTargetStack.addTask(task,
-                            !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
-                }
-            }
-        } else if (mStartActivity.mActivityComponent.equals(
-                intentActivity.getTaskRecord().realActivity)) {
-            // In this case the top activity on the task is the same as the one being launched,
-            // so we take that as a request to bring the task to the foreground. If the top
-            // activity in the task is the root activity, deliver this new intent to it if it
-            // desires.
-            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                        || LAUNCH_SINGLE_TOP == mLaunchMode)
-                    && intentActivity.mActivityComponent.equals(
-                            mStartActivity.mActivityComponent)) {
-                if (intentActivity.isRootOfTask()) {
-                    intentActivity.getTaskRecord().setIntent(mStartActivity);
-                }
-                deliverNewIntent(intentActivity);
-            } else if (!intentActivity.getTaskRecord().isSameIntentFilter(mStartActivity)) {
-                // In this case we are launching the root activity of the task, but with a
-                // different intent. We should start a new instance on top.
-                mAddingToTask = true;
-                mSourceRecord = intentActivity;
-            }
-        } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
-            // In this case an activity is being launched in to an existing task, without
-            // resetting that task. This is typically the situation of launching an activity
-            // from a notification or shortcut. We want to place the new activity on top of the
-            // current task.
-            mAddingToTask = true;
-            mSourceRecord = intentActivity;
-        } else if (!intentActivity.getTaskRecord().rootWasReset) {
-            // In this case we are launching into an existing task that has not yet been started
-            // from its front door. The current task has been brought to the front. Ideally,
-            // we'd probably like to place this new task at the bottom of its stack, but that's
-            // a little hard to do with the current organization of the code so for now we'll
-            // just drop it.
-            intentActivity.getTaskRecord().setIntent(mStartActivity);
-        }
     }
 
     private void resumeTargetStackIfNeeded() {
@@ -2308,47 +2372,24 @@
         mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);
     }
 
-    private int setTaskFromReuseOrCreateNewTask(TaskRecord taskToAffiliate) {
-        if (mRestrictedBgActivity && (mReuseTask == null || !mReuseTask.containsAppUid(mCallingUid))
-                && handleBackgroundActivityAbort(mStartActivity)) {
-            return START_ABORTED;
-        }
+    private void setNewTask(TaskRecord taskToAffiliate) {
+        final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
+        final TaskRecord task = mTargetStack.createTaskRecord(
+                mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId),
+                mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
+                mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
+                mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
+        addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
+        updateBounds(mStartActivity.getTaskRecord(), mLaunchParams.mBounds);
 
-        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
-
-        // Do no move the target stack to front yet, as we might bail if
-        // isLockTaskModeViolation fails below.
-
-        if (mReuseTask == null) {
-            final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
-            final TaskRecord task = mTargetStack.createTaskRecord(
-                    mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId),
-                    mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
-                    mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
-                    mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
-            addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
-            updateBounds(mStartActivity.getTaskRecord(), mLaunchParams.mBounds);
-
-            if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
+        if (DEBUG_TASKS) {
+            Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                     + " in new task " + mStartActivity.getTaskRecord());
-        } else {
-            addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");
         }
 
         if (taskToAffiliate != null) {
             mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
         }
-
-        if (mService.getLockTaskController().isLockTaskModeViolation(
-                mStartActivity.getTaskRecord())) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        if (mDoResume) {
-            mTargetStack.moveToFront("reuseOrNewTask");
-        }
-        return START_SUCCESS;
     }
 
     private void deliverNewIntent(ActivityRecord activity) {
@@ -2362,166 +2403,6 @@
         mIntentDelivered = true;
     }
 
-    private int setTaskFromSourceRecord() {
-        if (mService.getLockTaskController().isLockTaskModeViolation(
-                mSourceRecord.getTaskRecord())) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        final TaskRecord sourceTask = mSourceRecord.getTaskRecord();
-        final ActivityStack sourceStack = mSourceRecord.getActivityStack();
-        if (mRestrictedBgActivity && !sourceTask.containsAppUid(mCallingUid)) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        // We only want to allow changing stack in two cases:
-        // 1. If the target task is not the top one. Otherwise we would move the launching task to
-        //    the other side, rather than show two side by side.
-        // 2. If activity is not allowed on target display.
-        final int targetDisplayId = mTargetStack != null ? mTargetStack.mDisplayId
-                : sourceStack.mDisplayId;
-        final boolean moveStackAllowed = sourceStack.topTask() != sourceTask
-                || !mStartActivity.canBeLaunchedOnDisplay(targetDisplayId);
-        if (moveStackAllowed) {
-            mTargetStack = getLaunchStack(mStartActivity, mLaunchFlags,
-                    mStartActivity.getTaskRecord(), mOptions);
-            // If target stack is not found now - we can't just rely on the source stack, as it may
-            // be not suitable. Let's check other displays.
-            if (mTargetStack == null && targetDisplayId != sourceStack.mDisplayId) {
-                // Can't use target display, lets find a stack on the source display.
-                mTargetStack = mRootActivityContainer.getValidLaunchStackOnDisplay(
-                        sourceStack.mDisplayId, mStartActivity, mOptions, mLaunchParams);
-            }
-            if (mTargetStack == null) {
-                // There are no suitable stacks on the target and source display(s). Look on all
-                // displays.
-                mTargetStack = mRootActivityContainer.getNextValidLaunchStack(
-                        mStartActivity, -1 /* currentFocus */);
-            }
-        }
-
-        if (mTargetStack == null) {
-            mTargetStack = sourceStack;
-        } else if (mTargetStack != sourceStack) {
-            sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                    DEFER_RESUME, "launchToSide");
-        }
-
-        final TaskRecord topTask = mTargetStack.topTask();
-        if (topTask != sourceTask && !mAvoidMoveToFront) {
-            mTargetStack.moveTaskToFrontLocked(sourceTask, mNoAnimation, mOptions,
-                    mStartActivity.appTimeTracker, "sourceTaskToFront");
-        } else if (mDoResume) {
-            mTargetStack.moveToFront("sourceStackToFront");
-        }
-
-        if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0) {
-            // In this case, we are adding the activity to an existing task, but the caller has
-            // asked to clear that task if the activity is already running.
-            ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags);
-            mKeepCurTransition = true;
-            if (top != null) {
-                mStartActivity.logStartActivity(AM_NEW_INTENT, top.getTaskRecord());
-                deliverNewIntent(top);
-                // For paranoia, make sure we have correctly resumed the top activity.
-                mTargetStack.mLastPausedActivity = null;
-                if (mDoResume) {
-                    mRootActivityContainer.resumeFocusedStacksTopActivities();
-                }
-                ActivityOptions.abort(mOptions);
-                return START_DELIVERED_TO_TOP;
-            }
-        } else if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
-            // In this case, we are launching an activity in our own task that may already be
-            // running somewhere in the history, and we want to shuffle it to the front of the
-            // stack if so.
-            final ActivityRecord top = sourceTask.findActivityInHistoryLocked(mStartActivity);
-            if (top != null) {
-                final TaskRecord task = top.getTaskRecord();
-                task.moveActivityToFrontLocked(top);
-                top.updateOptionsLocked(mOptions);
-                mStartActivity.logStartActivity(AM_NEW_INTENT, task);
-                deliverNewIntent(top);
-                mTargetStack.mLastPausedActivity = null;
-                if (mDoResume) {
-                    mRootActivityContainer.resumeFocusedStacksTopActivities();
-                }
-                return START_DELIVERED_TO_TOP;
-            }
-        }
-
-        // An existing activity is starting this new activity, so we want to keep the new one in
-        // the same task as the one that is starting it.
-        addOrReparentStartingActivity(sourceTask, "setTaskFromSourceRecord");
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in existing task " + mStartActivity.getTaskRecord()
-                + " from source " + mSourceRecord);
-        return START_SUCCESS;
-    }
-
-    private int setTaskFromInTask() {
-        // The caller is asking that the new activity be started in an explicit
-        // task it has provided to us.
-        if (mService.getLockTaskController().isLockTaskModeViolation(mInTask)) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-        }
-
-        mTargetStack = mInTask.getStack();
-
-        // Check whether we should actually launch the new activity in to the task,
-        // or just reuse the current activity on top.
-        ActivityRecord top = mInTask.getTopActivity();
-        if (top != null && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
-                && top.mUserId == mStartActivity.mUserId) {
-            if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                    || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) {
-                mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
-                        mStartActivity.appTimeTracker, "inTaskToFront");
-                if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
-                    // We don't need to start a new activity, and the client said not to do
-                    // anything if that is the case, so this is it!
-                    return START_RETURN_INTENT_TO_CALLER;
-                }
-                deliverNewIntent(top);
-                return START_DELIVERED_TO_TOP;
-            }
-        }
-
-        if (!mAddingToTask) {
-            mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
-                    mStartActivity.appTimeTracker, "inTaskToFront");
-            // We don't actually want to have this activity added to the task, so just
-            // stop here but still tell the caller that we consumed the intent.
-            ActivityOptions.abort(mOptions);
-            return START_TASK_TO_FRONT;
-        }
-
-        if (!mLaunchParams.mBounds.isEmpty()) {
-            // TODO: Shouldn't we already know what stack to use by the time we get here?
-            ActivityStack stack = mRootActivityContainer.getLaunchStack(
-                    null, null, mInTask, ON_TOP);
-            if (stack != mInTask.getStack()) {
-                mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                        DEFER_RESUME, "inTaskToFront");
-                mTargetStack = mInTask.getStack();
-            }
-
-            updateBounds(mInTask, mLaunchParams.mBounds);
-        }
-
-        mTargetStack.moveTaskToFrontLocked(
-                mInTask, mNoAnimation, mOptions, mStartActivity.appTimeTracker, "inTaskToFront");
-
-        addOrReparentStartingActivity(mInTask, "setTaskFromInTask");
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in explicit task " + mStartActivity.getTaskRecord());
-
-        return START_SUCCESS;
-    }
-
     @VisibleForTesting
     void updateBounds(TaskRecord task, Rect bounds) {
         if (bounds.isEmpty()) {
@@ -2529,41 +2410,13 @@
         }
 
         final ActivityStack stack = task.getStack();
-        if (stack != null && stack.resizeStackWithLaunchBounds()) {
-            mService.resizeStack(
-                    stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        if (stack != null && stack.inPinnedWindowingMode()) {
+            mService.animateResizePinnedStack(stack.mStackId, bounds, -1);
         } else {
-            task.updateOverrideConfiguration(bounds);
+            task.setBounds(bounds);
         }
     }
 
-    private int setTaskToCurrentTopOrCreateNewTask() {
-        mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
-        if (mDoResume) {
-            mTargetStack.moveToFront("addingToTopTask");
-        }
-        final ActivityRecord prev = mTargetStack.getTopActivity();
-        if (mRestrictedBgActivity && prev == null) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        final TaskRecord task = (prev != null)
-                ? prev.getTaskRecord() : mTargetStack.createTaskRecord(
-                mSupervisor.getNextTaskIdForUserLocked(mStartActivity.mUserId), mStartActivity.info,
-                mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
-        if (mRestrictedBgActivity && prev != null && !task.containsAppUid(mCallingUid)) {
-            if (handleBackgroundActivityAbort(mStartActivity)) {
-                return START_ABORTED;
-            }
-        }
-        addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
-        mTargetStack.positionChildWindowContainerAtTop(task);
-        if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
-                + " in new guessed " + mStartActivity.getTaskRecord());
-        return START_SUCCESS;
-    }
-
     private void addOrReparentStartingActivity(TaskRecord parent, String reason) {
         if (mStartActivity.getTaskRecord() == null || mStartActivity.getTaskRecord() == parent) {
             parent.addActivityToTop(mStartActivity);
@@ -2695,7 +2548,8 @@
             final boolean onTop =
                     (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
             final ActivityStack stack =
-                    mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams);
+                    mRootActivityContainer.getLaunchStack(r, aOptions, task, onTop, mLaunchParams,
+                            mRequest.realCallingPid, mRequest.realCallingUid);
             return stack;
         }
         // Otherwise handle adjacent launch.
@@ -2813,11 +2667,24 @@
         return this;
     }
 
+    /**
+     * Sets the pid of the caller who originally started the activity.
+     *
+     * Normally, the pid/uid would be the calling pid from the binder call.
+     * However, in case of a {@link PendingIntent}, the pid/uid pair of the caller is considered
+     * the original entity that created the pending intent, in contrast to setRealCallingPid/Uid,
+     * which represents the entity who invoked pending intent via {@link PendingIntent#send}.
+     */
     ActivityStarter setCallingPid(int pid) {
         mRequest.callingPid = pid;
         return this;
     }
 
+    /**
+     * Sets the uid of the caller who originally started the activity.
+     *
+     * @see #setCallingPid
+     */
     ActivityStarter setCallingUid(int uid) {
         mRequest.callingUid = uid;
         return this;
@@ -2828,11 +2695,25 @@
         return this;
     }
 
+    /**
+     * Sets the pid of the caller who requested to launch the activity.
+     *
+     * The pid/uid represents the caller who launches the activity in this request.
+     * It will almost same as setCallingPid/Uid except when processing {@link PendingIntent}:
+     * the pid/uid will be the caller who called {@link PendingIntent#send()}.
+     *
+     * @see #setCallingPid
+     */
     ActivityStarter setRealCallingPid(int pid) {
         mRequest.realCallingPid = pid;
         return this;
     }
 
+    /**
+     * Sets the uid of the caller who requested to launch the activity.
+     *
+     * @see #setRealCallingPid
+     */
     ActivityStarter setRealCallingUid(int uid) {
         mRequest.realCallingUid = uid;
         return this;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f2ca2ba8..468a13d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -19,6 +19,8 @@
 import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
@@ -45,7 +47,6 @@
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION;
 import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
 import static android.os.FactoryTest.FACTORY_TEST_OFF;
@@ -87,7 +88,6 @@
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
 import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
@@ -123,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;
@@ -214,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;
@@ -547,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.
@@ -596,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;
@@ -774,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);
-            }
         }
     }
 
@@ -1616,8 +1621,7 @@
                     // Explicitly dismissing the activity so reset its relaunch flag.
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
                 } else {
-                    r.finishIfPossible(resultCode, resultData, "app-request",
-                            true /* oomAdj */, !PAUSE_IMMEDIATELY);
+                    r.finishIfPossible(resultCode, resultData, "app-request", true /* oomAdj */);
                     res = r.finishing;
                     if (!res) {
                         Slog.i(TAG, "Failed to finish by app-request");
@@ -1682,7 +1686,6 @@
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
             ActivityRecord.activityResumedLocked(token);
-            mWindowManager.notifyAppResumedFinished(token);
         }
         Binder.restoreCallingIdentity(origId);
     }
@@ -2510,15 +2513,22 @@
             @WindowConfiguration.ActivityType int ignoreActivityType,
             @WindowConfiguration.WindowingMode int ignoreWindowingMode) {
         final int callingUid = Binder.getCallingUid();
+        final int callingPid = Binder.getCallingPid();
+        final boolean crossUser = isCrossUserAllowed(callingPid, callingUid);
+        final int[] profileIds = getUserManager().getProfileIds(
+                UserHandle.getUserId(callingUid), true);
+        ArraySet<Integer> callingProfileIds = new ArraySet<>();
+        for (int i = 0; i < profileIds.length; i++) {
+            callingProfileIds.add(profileIds[i]);
+        }
         ArrayList<ActivityManager.RunningTaskInfo> list = new ArrayList<>();
 
         synchronized (mGlobalLock) {
             if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
 
-            final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
-                    callingUid);
+            final boolean allowed = isGetTasksAllowed("getTasks", callingPid, callingUid);
             mRootActivityContainer.getRunningTasks(maxNum, list, ignoreActivityType,
-                    ignoreWindowingMode, callingUid, allowed);
+                    ignoreWindowingMode, callingUid, allowed, crossUser, callingProfileIds);
         }
 
         return list;
@@ -2584,35 +2594,23 @@
     }
 
     @Override
-    public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
-            boolean preserveWindows, boolean animate, int animationDuration) {
-        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+    public void animateResizePinnedStack(int stackId, Rect destBounds, int animationDuration) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "animateResizePinnedStack()");
 
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                if (animate) {
-                    final ActivityStack stack = mRootActivityContainer.getStack(stackId);
-                    if (stack == null) {
-                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-                        return;
-                    }
-                    if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
-                        throw new IllegalArgumentException("Stack: " + stackId
-                                + " doesn't support animated resize.");
-                    }
-                    stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
-                            animationDuration, false /* fromFullscreen */);
-                } else {
-                    final ActivityStack stack = mRootActivityContainer.getStack(stackId);
-                    if (stack == null) {
-                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-                        return;
-                    }
-                    mRootActivityContainer.resizeStack(stack, destBounds,
-                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                            preserveWindows, allowResizeInDockedMode, !DEFER_RESUME);
+                final ActivityStack stack = mRootActivityContainer.getStack(stackId);
+                if (stack == null) {
+                    Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+                    return;
                 }
+                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    throw new IllegalArgumentException("Stack: " + stackId
+                        + " doesn't support animated resize.");
+                }
+                stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+                        animationDuration, false /* fromFullscreen */);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -2910,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);
@@ -3548,6 +3546,11 @@
         return allowed;
     }
 
+    boolean isCrossUserAllowed(int pid, int uid) {
+        return checkPermission(INTERACT_ACROSS_USERS, pid, uid) == PERMISSION_GRANTED
+                || checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) == PERMISSION_GRANTED;
+    }
+
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
             IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
@@ -4387,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 {
@@ -5098,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,
@@ -5109,9 +5112,7 @@
 
             kept = ensureConfigAndVisibilityAfterUpdate(starting, changes);
         } finally {
-            if (mWindowManager != null) {
-                mWindowManager.continueSurfaceLayout();
-            }
+            continueWindowLayout();
         }
 
         if (result != null) {
@@ -5239,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);
     }
@@ -5324,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;
             }
@@ -5460,7 +5489,7 @@
             updateOomAdj = true;
         }
         if (updateOomAdj) {
-            mH.post(mAmInternal::updateOomAdj);
+            updateOomAdj();
         }
     }
 
@@ -5923,6 +5952,10 @@
         return allUids.contains(uid);
     }
 
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        mTaskChangeNotificationController.notifySingleTaskDisplayEmpty(displayId);
+    }
+
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
 
@@ -6662,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
@@ -6673,7 +6706,7 @@
                                     !PRESERVE_WINDOWS);
                         }
                     } finally {
-                        mWindowManager.continueSurfaceLayout();
+                        continueWindowLayout();
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index be8a0bd..278a9ba 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.annotation.ColorInt;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -41,11 +40,6 @@
     boolean getShowWallpaper();
 
     /**
-     * @return The background color behind the animation.
-     */
-    @ColorInt int getBackgroundColor();
-
-    /**
      * Requests to start the animation.
      *
      * @param animationLeash The surface to run the animation on. See {@link SurfaceAnimator} for an
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 557a609..66d52cc 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1999,8 +1999,8 @@
                             mNextAppTransitionFutureCallback, null /* finishedCallback */,
                             mNextAppTransitionScaleUp);
                     mNextAppTransitionFutureCallback = null;
+                    mService.requestTraversal();
                 }
-                mService.requestTraversal();
             });
         }
     }
diff --git a/services/core/java/com/android/server/wm/AppWindowThumbnail.java b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
index 4ceae72..b52ade4 100644
--- a/services/core/java/com/android/server/wm/AppWindowThumbnail.java
+++ b/services/core/java/com/android/server/wm/AppWindowThumbnail.java
@@ -41,6 +41,8 @@
 
 import com.android.server.wm.SurfaceAnimator.Animatable;
 
+import java.util.function.Supplier;
+
 /**
  * Represents a surface that is displayed over an {@link AppWindowToken}
  */
@@ -55,8 +57,9 @@
     private final int mHeight;
     private final boolean mRelative;
 
-    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader) {
-        this(t, appToken, thumbnailHeader, false /* relative */);
+    AppWindowThumbnail(Supplier<Surface> surfaceFactory, Transaction t, AppWindowToken appToken,
+            GraphicBuffer thumbnailHeader) {
+        this(surfaceFactory, t, appToken, thumbnailHeader, false /* relative */);
     }
 
     /**
@@ -66,9 +69,9 @@
      * @param relative Whether this thumbnail will be a child of appToken (and thus positioned
      *                 relative to it) or not.
      */
-    AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
-            boolean relative) {
-        this(t, appToken, thumbnailHeader, relative, new Surface(), null);
+    AppWindowThumbnail(Supplier<Surface> surfaceFactory, Transaction t, AppWindowToken appToken,
+            GraphicBuffer thumbnailHeader, boolean relative) {
+        this(t, appToken, thumbnailHeader, relative, surfaceFactory.get(), null);
     }
 
     AppWindowThumbnail(Transaction t, AppWindowToken appToken, GraphicBuffer thumbnailHeader,
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 7e0d9a0..f647fe4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -955,6 +955,9 @@
             updateReportedVisibilityLocked();
         }
 
+        // Reset the last saved PiP snap fraction on removal.
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
+
         mRemovingFromDisplay = false;
     }
 
@@ -1021,7 +1024,7 @@
         if (DEBUG_ADD_REMOVE) Slog.v(TAG, "notifyAppStopped: " + this);
         mAppStopped = true;
         // Reset the last saved PiP snap fraction on app stop.
-        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
+        mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(mActivityComponent);
         destroySurfaces();
         // Remove any starting window that was added for this app if they are still around.
         removeStartingWindow();
@@ -1705,10 +1708,7 @@
             return;
         }
 
-        if (prevWinMode != WINDOWING_MODE_UNDEFINED && winMode == WINDOWING_MODE_PINNED) {
-            // Entering PiP from fullscreen, reset the snap fraction
-            mDisplayContent.mPinnedStackControllerLocked.resetReentrySnapFraction(this);
-        } else if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
+        if (prevWinMode == WINDOWING_MODE_PINNED && winMode != WINDOWING_MODE_UNDEFINED
                 && !isHidden()) {
             // Leaving PiP to fullscreen, save the snap fraction based on the pre-animation bounds
             // for the next re-entry into PiP (assuming the activity is not hidden or destroyed)
@@ -1726,8 +1726,8 @@
                     stackBounds = mTmpRect;
                     pinnedStack.getBounds(stackBounds);
                 }
-                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(this,
-                        stackBounds);
+                mDisplayContent.mPinnedStackControllerLocked.saveReentrySnapFraction(
+                        mActivityComponent, stackBounds);
             }
         } else if (shouldStartChangeTransition(prevWinMode, winMode)) {
             initializeChangeTransition(mTmpPrevBounds);
@@ -1793,8 +1793,8 @@
                     mWmService.mTaskSnapshotController.createTaskSnapshot(
                             task, 1 /* scaleFraction */);
             if (snapshot != null) {
-                mThumbnail = new AppWindowThumbnail(t, this, snapshot.getGraphicBuffer(),
-                        true /* relative */);
+                mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory, t, this,
+                        snapshot.getGraphicBuffer(), true /* relative */);
             }
         }
     }
@@ -2033,7 +2033,8 @@
         final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent();
         if (needsLetterbox) {
             if (mLetterbox == null) {
-                mLetterbox = new Letterbox(() -> makeChildSurface(null));
+                mLetterbox = new Letterbox(() -> makeChildSurface(null),
+                        mWmService.mTransactionFactory);
                 mLetterbox.attachInput(w);
             }
             getPosition(mTmpPoint);
@@ -2502,14 +2503,18 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
-        // All normal app transitions take place in an animation layer which is below the pinned
-        // stack but may be above the parent stacks of the given animating apps.
         // For transitions in the pinned stack (menu activity) we just let them occur as a child
         // of the pinned stack.
-        if (!inPinnedWindowingMode()) {
-            return getAppAnimationLayer();
-        } else {
+        // All normal app transitions take place in an animation layer which is below the pinned
+        // stack but may be above the parent stacks of the given animating apps by default. When
+        // a new hierarchical animation is enabled, we just let them occur as a child of the parent
+        // stack, i.e. the hierarchy of the surfaces is unchanged.
+        if (inPinnedWindowingMode()) {
             return getStack().getSurfaceControl();
+        } else if (WindowManagerService.sHierarchicalAnimations) {
+            return super.getAnimationLeashParent();
+        } else {
+            return getAppAnimationLayer();
         }
     }
 
@@ -2981,7 +2986,8 @@
             return;
         }
         clearThumbnail();
-        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnailHeader);
+        mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory, getPendingTransaction(),
+                this, thumbnailHeader);
         mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
     }
 
@@ -3009,7 +3015,8 @@
         if (thumbnail == null) {
             return;
         }
-        mThumbnail = new AppWindowThumbnail(getPendingTransaction(), this, thumbnail);
+        mThumbnail = new AppWindowThumbnail(mWmService.mSurfaceFactory,
+                getPendingTransaction(), this, thumbnail);
         final Animation animation =
                 getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
                         win.getFrameLw());
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
index 90bb494..5c4332d 100644
--- a/services/core/java/com/android/server/wm/BarController.java
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -27,6 +27,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.WindowManager;
 
 import com.android.server.LocalServices;
@@ -89,6 +90,10 @@
     }
 
     void setWindow(WindowState win) {
+        if (ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL) {
+            // BarController gets replaced with InsetsPolicy in the full insets mode.
+            return;
+        }
         mWin = win;
     }
 
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 7fc17e1..7557271a 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -27,6 +27,7 @@
 import android.view.SurfaceControl;
 
 import java.io.PrintWriter;
+import java.util.function.Supplier;
 
 /**
  * Four black surfaces put together to make a black frame.
@@ -97,7 +98,7 @@
     final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
 
     final boolean mForceDefaultOrientation;
-    private final TransactionFactory mTransactionFactory;
+    private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
 
     public void printTo(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw);
@@ -112,8 +113,8 @@
         }
     }
 
-    public BlackFrame(TransactionFactory factory, SurfaceControl.Transaction t, Rect outer,
-            Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
+    public BlackFrame(Supplier<SurfaceControl.Transaction> factory, SurfaceControl.Transaction t,
+            Rect outer, Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
             throws OutOfResourcesException {
         boolean success = false;
 
@@ -151,7 +152,7 @@
 
     public void kill() {
         if (mBlackSurfaces != null) {
-            SurfaceControl.Transaction t = mTransactionFactory.make();
+            SurfaceControl.Transaction t = mTransactionFactory.get();
             for (int i=0; i<mBlackSurfaces.length; i++) {
                 if (mBlackSurfaces[i] != null) {
                     if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index c3d6211..c1ca816 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -34,6 +34,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class CircularDisplayMask {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
 
@@ -43,7 +45,7 @@
     private Point mScreenSize;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
@@ -53,10 +55,10 @@
     private boolean mDimensionsUnequal = false;
     private int mMaskThickness;
 
-    public CircularDisplayMask(DisplayContent dc, int zOrder,
+    CircularDisplayMask(Supplier<Surface> surfaceFactory, DisplayContent dc, int zOrder,
             int screenOffset, int maskThickness) {
         final Display display = dc.getDisplay();
-
+        mSurface = surfaceFactory.get();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
         if (mScreenSize.x != mScreenSize.y + screenOffset) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 71a0126..63ff2ea 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -100,6 +100,7 @@
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.STACKS;
 import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -549,6 +550,7 @@
     private final PointerEventDispatcher mPointerEventDispatcher;
 
     private final InsetsStateController mInsetsStateController;
+    private final InsetsPolicy mInsetsPolicy;
 
     /** @see #getParentWindow() */
     private WindowState mParentWindow;
@@ -581,27 +583,6 @@
         }
     };
 
-    private final Consumer<WindowState> mUpdateWallpaperForAnimator = w -> {
-        final WindowStateAnimator winAnimator = w.mWinAnimator;
-        if (winAnimator.mSurfaceController == null || !winAnimator.hasSurface()) {
-            return;
-        }
-
-        // If this window is animating, ensure the animation background is set.
-        final AnimationAdapter anim = w.mAppToken != null
-                ? w.mAppToken.getAnimation()
-                : w.getAnimation();
-        if (anim != null) {
-            final int color = anim.getBackgroundColor();
-            if (color != 0) {
-                final TaskStack stack = w.getStack();
-                if (stack != null) {
-                    stack.setAnimationBackground(winAnimator, color);
-                }
-            }
-        }
-    };
-
     private final Consumer<WindowState> mScheduleToastTimeout = w -> {
         final int lostFocusUid = mTmpWindow.mOwnerUid;
         final Handler handler = mWmService.mH;
@@ -941,14 +922,17 @@
         final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(mSession)
                 .setOpaque(true)
                 .setContainerLayer();
-        mWindowingLayer = b.setName("Display Root").build();
-        mOverlayLayer = b.setName("Display Overlays").build();
+        mSurfaceControl = b.setName("Root").setContainerLayer().build();
+        mWindowingLayer = b.setName("Display Windows").setParent(mSurfaceControl).build();
+        mOverlayLayer = b.setName("Display Overlays").setParent(mSurfaceControl).build();
 
-        getPendingTransaction().setLayer(mWindowingLayer, 0)
-                .setLayerStack(mWindowingLayer, mDisplayId)
+        getPendingTransaction()
+                .setLayer(mSurfaceControl, 0)
+                .setLayerStack(mSurfaceControl, mDisplayId)
+                .show(mSurfaceControl)
+                .setLayer(mWindowingLayer, 0)
                 .show(mWindowingLayer)
                 .setLayer(mOverlayLayer, 1)
-                .setLayerStack(mOverlayLayer, mDisplayId)
                 .show(mOverlayLayer);
         getPendingTransaction().apply();
 
@@ -968,6 +952,7 @@
         mWmService.mAnimator.addDisplayLocked(mDisplayId);
         mInputMonitor = new InputMonitor(service, mDisplayId);
         mInsetsStateController = new InsetsStateController(this);
+        mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
     }
 
     boolean isReady() {
@@ -1054,14 +1039,21 @@
                 // removing from parent.
                 token.getParent().removeChild(token);
             }
-            if (prevDc.mLastFocus == mCurrentFocus) {
-                // The window has become the focus of this display, so it should not be notified
-                // that it lost focus from the previous display.
+            if (token.hasChild(prevDc.mLastFocus)) {
+                // If the reparent window token contains previous display's last focus window, means
+                // it will end up to gain window focus on the target display, so it should not be
+                // notified that it lost focus from the previous display.
                 prevDc.mLastFocus = null;
             }
         }
 
         addWindowToken(token.token, token);
+
+        if (mWmService.mAccessibilityController != null) {
+            final int prevDisplayId = prevDc != null ? prevDc.getDisplayId() : INVALID_DISPLAY;
+            mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(prevDisplayId,
+                    getDisplayId());
+        }
     }
 
     void removeAppToken(IBinder binder) {
@@ -1120,6 +1112,10 @@
         return mInsetsStateController;
     }
 
+    InsetsPolicy getInsetsPolicy() {
+        return mInsetsPolicy;
+    }
+
     @Surface.Rotation
     int getRotation() {
         return mDisplayRotation.getRotation();
@@ -2150,7 +2146,6 @@
      *               so only need to configure display.
      */
     void setForcedDensity(int density, int userId) {
-        final boolean clear = density == mInitialDisplayDensity;
         final boolean updateCurrent = userId == UserHandle.USER_CURRENT;
         if (mWmService.mCurrentUserId == userId || updateCurrent) {
             mBaseDisplayDensity = density;
@@ -2367,12 +2362,6 @@
         mDisplayPolicy.switchUser();
     }
 
-    private void resetAnimationBackgroundAnimator() {
-        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
-        }
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -2904,9 +2893,11 @@
             mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
         }
 
-        if (DEBUG_FOCUS_LIGHT || mWmService.localLOGV) Slog.v(TAG_WM, "Changing focus from "
-                + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId()
-                + " Callers=" + Debug.getCallers(4));
+        if (DEBUG_FOCUS_LIGHT || DEBUG) {
+            Slog.v(TAG_WM, "Changing focus from "
+                    + mCurrentFocus + " to " + newFocus + " displayId=" + getDisplayId()
+                    + " Callers=" + Debug.getCallers(4));
+        }
         final WindowState oldFocus = mCurrentFocus;
         mCurrentFocus = newFocus;
         mLosingFocus.remove(newFocus);
@@ -3404,14 +3395,6 @@
         forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
     }
 
-    /**
-     * Updates the {@link TaskStack#setAnimationBackground} for all windows.
-     */
-    void updateBackgroundForAnimator() {
-        resetAnimationBackgroundAnimator();
-        forAllWindows(mUpdateWallpaperForAnimator, true /* traverseTopToBottom */);
-    }
-
     boolean isInputMethodClientFocus(int uid, int pid) {
         final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
@@ -3865,21 +3848,6 @@
         }
     }
 
-    private static final class ScreenshotApplicationState {
-        WindowState appWin;
-        int maxLayer;
-        int minLayer;
-        boolean screenshotReady;
-
-        void reset(boolean screenshotReady) {
-            appWin = null;
-            maxLayer = 0;
-            minLayer = 0;
-            this.screenshotReady = screenshotReady;
-            minLayer = (screenshotReady) ? 0 : Integer.MAX_VALUE;
-        }
-    }
-
     /**
      * Base class for any direct child window container of {@link #DisplayContent} need to inherit
      * from. This is mainly a pass through class that allows {@link #DisplayContent} to have
@@ -4388,7 +4356,7 @@
                         .show(mSplitScreenDividerAnchor);
                 scheduleAnimation();
             } else {
-                mWmService.mTransactionFactory.make()
+                mWmService.mTransactionFactory.get()
                         .remove(mAppAnimationLayer)
                         .remove(mBoostedAppAnimationLayer)
                         .remove(mHomeAppAnimationLayer)
@@ -4867,7 +4835,7 @@
             mPortalWindowHandle = createPortalWindowHandle(sc.toString());
         }
         getPendingTransaction().setInputWindowInfo(sc, mPortalWindowHandle)
-                .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc);
+                .reparent(mSurfaceControl, sc);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8328770..10d48c4 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -103,7 +103,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.localLOGV;
 
 import android.Manifest.permission;
 import android.annotation.NonNull;
@@ -336,7 +335,6 @@
 
     private static final Rect sTmpDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
     private static final Rect sTmpRect = new Rect();
-    private static final Rect sTmpDockedFrame = new Rect();
     private static final Rect sTmpNavFrame = new Rect();
     private static final Rect sTmpLastParentFrame = new Rect();
 
@@ -1029,6 +1027,14 @@
                 displayFrames.mDisplayCutoutSafe.top);
     }
 
+    WindowState getStatusBar() {
+        return mStatusBar;
+    }
+
+    WindowState getNavigationBar() {
+        return mNavigationBar;
+    }
+
     /**
      * Control the animation to run when a window's state changes.  Return a
      * non-0 number to force the animation to a specific resource ID, or 0
@@ -2589,7 +2595,7 @@
         }
         final int fl = PolicyControl.getWindowFlags(null,
                 mTopFullscreenOpaqueWindowState.getAttrs());
-        if (localLOGV) {
+        if (WindowManagerDebugConfig.DEBUG) {
             Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
             Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
                     + " lp.flags=0x" + Integer.toHexString(fl));
@@ -3108,8 +3114,7 @@
             return 0;
         }
 
-        mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(
-                mTopFullscreenOpaqueWindowState);
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
 
         int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
                 & ~mResettingSystemUiFlags
@@ -3544,7 +3549,8 @@
         if (mScreenshotHelper != null) {
             mScreenshotHelper.takeScreenshot(screenshotType,
                     mStatusBar != null && mStatusBar.isVisibleLw(),
-                    mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
+                    mNavigationBar != null && mNavigationBar.isVisibleLw(),
+                    mHandler, null /* completionConsumer */);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 120ce3e..ae3b5f2 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -36,7 +36,6 @@
 import static com.android.server.wm.DockedStackDividerControllerProto.MINIMIZED_DOCK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -141,8 +140,6 @@
     float mLastDividerProgress;
     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
     private boolean mImeHideRequested;
-    private final Rect mLastDimLayerRect = new Rect();
-    private float mLastDimLayerAlpha;
     private TaskStack mDimmedStack;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
@@ -656,14 +653,6 @@
     }
 
     /**
-     * @return The layer used for dimming the apps when dismissing docked/fullscreen stack. Just
-     *         above all application surfaces.
-     */
-    private int getResizeDimLayer() {
-        return (mWindow != null) ? mWindow.mLayer - 1 : LAYER_OFFSET_DIM;
-    }
-
-    /**
      * Notifies the docked stack divider controller of a visibility change that happens without
      * an animation.
      */
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 20a1333..c5c2364 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.UriGrantsManager;
 import android.content.ClipData;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index c48f07c..17daabf 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -140,7 +140,7 @@
         mFlags = flags;
         mLocalWin = localWin;
         mNotifiedWindows = new ArrayList<WindowState>();
-        mTransaction = service.mTransactionFactory.make();
+        mTransaction = service.mTransactionFactory.get();
     }
 
     boolean isClosing() {
@@ -695,7 +695,8 @@
             implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
-            try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+            try (SurfaceControl.Transaction transaction =
+                         mService.mTransactionFactory.get()) {
                 transaction.setPosition(
                         mSurfaceControl,
                         (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 7cb4a43..f64592f 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -32,6 +32,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class EmulatorDisplayOverlay {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "EmulatorDisplayOverlay" : TAG_WM;
 
@@ -39,7 +41,7 @@
     private Point mScreenSize;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
@@ -47,8 +49,9 @@
     private int mRotation;
     private boolean mVisible;
 
-    public EmulatorDisplayOverlay(Context context, DisplayContent dc,
+    EmulatorDisplayOverlay(Supplier<Surface> surfaceFactory, Context context, DisplayContent dc,
             int zOrder) {
+        mSurface = surfaceFactory.get();
         final Display display = dc.getDisplay();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index 315de91..8ae740d 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,9 +58,9 @@
     @VisibleForTesting
     HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
         mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
-        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 BackgroundThread.getExecutor(), new OnPropertiesChangedListener());
-        final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+        final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 KEY_HIGH_REFRESH_RATE_BLACKLIST);
         updateBlacklist(property);
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 2eec926..dd9000e 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -153,7 +153,7 @@
         mService = service;
         mDisplayContent = mService.mRoot.getDisplayContent(displayId);
         mDisplayId = displayId;
-        mInputTransaction = mService.mTransactionFactory.make();
+        mInputTransaction = mService.mTransactionFactory.get();
         mHandler = AnimationThread.getHandler();
         mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
     }
diff --git a/services/core/java/com/android/server/wm/SurfaceFactory.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
similarity index 80%
rename from services/core/java/com/android/server/wm/SurfaceFactory.java
rename to services/core/java/com/android/server/wm/InsetsControlTarget.java
index 076b7df..3db6dcf 100644
--- a/services/core/java/com/android/server/wm/SurfaceFactory.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -16,12 +16,9 @@
 
 package com.android.server.wm;
 
-import android.view.Surface;
-
 /**
- * Helper class to inject custom {@link Surface} objects into window manager.
+ * Generalization of an object that can control insets state.
  */
-interface SurfaceFactory {
-    Surface make();
-};
-
+interface InsetsControlTarget {
+    void notifyInsetsControlChanged();
+}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
new file mode 100644
index 0000000..2dc50d8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+
+import android.annotation.Nullable;
+
+/**
+ * Policy that implements who gets control over the windows generating insets.
+ */
+class InsetsPolicy {
+
+    private final InsetsStateController mStateController;
+    private final DisplayContent mDisplayContent;
+    private final DisplayPolicy mPolicy;
+
+    InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
+        mStateController = stateController;
+        mDisplayContent = displayContent;
+        mPolicy = displayContent.getDisplayPolicy();
+    }
+
+    /** Updates the target which can control system bars. */
+    void updateBarControlTarget(@Nullable WindowState focusedWin) {
+        mStateController.onBarControlTargetChanged(getTopControlTarget(focusedWin),
+                getNavControlTarget(focusedWin));
+    }
+
+    private @Nullable InsetsControlTarget getTopControlTarget(@Nullable WindowState focusedWin) {
+        if (areSystemBarsForciblyVisible() || isStatusBarForciblyVisible()) {
+            return null;
+        }
+        return focusedWin;
+    }
+
+    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin) {
+        if (areSystemBarsForciblyVisible() || isNavBarForciblyVisible()) {
+            return null;
+        }
+        return focusedWin;
+    }
+
+    private boolean isStatusBarForciblyVisible() {
+        final WindowState statusBar = mPolicy.getStatusBar();
+        if (statusBar == null) {
+            return false;
+        }
+        final int privateFlags = statusBar.mAttrs.privateFlags;
+
+        // TODO: Pretend to the app that it's still able to control it?
+        if ((privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) {
+            return true;
+        }
+        if ((privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isNavBarForciblyVisible() {
+        final WindowState statusBar = mPolicy.getStatusBar();
+        if (statusBar == null) {
+            return false;
+        }
+        if ((statusBar.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean areSystemBarsForciblyVisible() {
+        final boolean isDockedStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final boolean isFreeformStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM);
+        final boolean isResizing = mDisplayContent.getDockedDividerController().isResizing();
+
+        // We need to force system bars when the docked stack is visible, when the freeform stack
+        // is visible but also when we are resizing for the transitions when docked stack
+        // visibility changes.
+        return isDockedStackVisible || isFreeformStackVisible || isResizing;
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index b7835aa..8426864 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -51,8 +51,11 @@
     private final @NonNull InsetsSource mSource;
     private final DisplayContent mDisplayContent;
     private final InsetsStateController mStateController;
+    private final InsetsSourceControl mFakeControl;
     private @Nullable InsetsSourceControl mControl;
-    private @Nullable WindowState mControllingWin;
+    private @Nullable InsetsControlTarget mControlTarget;
+    private @Nullable InsetsControlTarget mFakeControlTarget;
+
     private @Nullable ControlAdapter mAdapter;
     private WindowState mWin;
     private TriConsumer<DisplayFrames, WindowState, Rect> mFrameProvider;
@@ -73,6 +76,8 @@
         mSource = source;
         mDisplayContent = displayContent;
         mStateController = stateController;
+        mFakeControl = new InsetsSourceControl(source.getType(), null /* leash */,
+                new Point());
 
         final int type = source.getType();
         if (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR) {
@@ -119,8 +124,8 @@
             mSource.setFrame(new Rect());
         } else {
             mWin.setInsetProvider(this);
-            if (mControllingWin != null) {
-                updateControlForTarget(mControllingWin, true /* force */);
+            if (mControlTarget != null) {
+                updateControlForTarget(mControlTarget, true /* force */);
             }
         }
     }
@@ -143,19 +148,29 @@
         if (mControl != null) {
             final Rect frame = mWin.getWindowFrames().mFrame;
             if (mControl.setSurfacePosition(frame.left, frame.top)) {
-                mStateController.notifyControlChanged(mControllingWin);
+                mStateController.notifyControlChanged(mControlTarget);
             }
         }
         setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.isVisibleByPolicy()
                 && !mWin.mGivenInsetsPending);
     }
 
-    void updateControlForTarget(@Nullable WindowState target, boolean force) {
-        if (mWin == null) {
-            mControllingWin = target;
+    /**
+     * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
+     */
+    void updateControlForFakeTarget(@Nullable InsetsControlTarget fakeTarget) {
+        if (fakeTarget == mFakeControlTarget) {
             return;
         }
-        if (target == mControllingWin && !force) {
+        mFakeControlTarget = fakeTarget;
+    }
+
+    void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+        if (mWin == null) {
+            mControlTarget = target;
+            return;
+        }
+        if (target == mControlTarget && !force) {
             return;
         }
         if (target == null) {
@@ -167,13 +182,13 @@
         setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
         mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
                 !mClientVisible /* hidden */);
-        mControllingWin = target;
+        mControlTarget = target;
         mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
                 new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
     }
 
     boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
-        if (mControllingWin != caller || modifiedSource.isVisible() == mClientVisible) {
+        if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
             return false;
         }
         setClientVisible(modifiedSource.isVisible());
@@ -199,8 +214,14 @@
         mSource.setVisible(mServerVisible && mClientVisible);
     }
 
-    InsetsSourceControl getControl() {
-        return mControl;
+    InsetsSourceControl getControl(InsetsControlTarget target) {
+        if (target == mControlTarget) {
+            return mControl;
+        }
+        if (target == mFakeControlTarget) {
+            return mFakeControl;
+        }
+        return null;
     }
 
     boolean isClientVisible() {
@@ -217,11 +238,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             mCapturedLeash = animationLeash;
@@ -232,10 +248,10 @@
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
             if (mAdapter == this) {
-                mStateController.notifyControlRevoked(mControllingWin, InsetsSourceProvider.this);
+                mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this);
                 setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
                 mControl = null;
-                mControllingWin = null;
+                mControlTarget = null;
                 mAdapter = null;
             }
         }
@@ -257,5 +273,5 @@
         @Override
         public void writeToProto(ProtoOutputStream proto) {
         }
-    };
+    }
 }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index a1b52f4..4ebb553 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -16,11 +16,11 @@
 
 package com.android.server.wm;
 
+import static android.view.InsetsState.InternalInsetType;
 import static android.view.InsetsState.TYPE_IME;
 import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.TYPE_TOP_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
 import static android.view.ViewRootImpl.sNewInsetsMode;
 
 import android.annotation.NonNull;
@@ -31,7 +31,6 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.ViewRootImpl;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -46,10 +45,15 @@
     private final InsetsState mState = new InsetsState();
     private final DisplayContent mDisplayContent;
 
-    private final ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>();
-    private final ArrayMap<WindowState, ArrayList<Integer>> mWinControlTypeMap = new ArrayMap<>();
-    private final SparseArray<WindowState> mTypeWinControlMap = new SparseArray<>();
-    private final ArraySet<WindowState> mPendingControlChanged = new ArraySet<>();
+    private final ArrayMap<Integer, InsetsSourceProvider> mProviders = new ArrayMap<>();
+    private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap =
+            new ArrayMap<>();
+    private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>();
+
+    /** @see #onControlFakeTargetChanged */
+    private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>();
+
+    private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
 
     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
         if (w.isVisible()) {
@@ -87,15 +91,15 @@
         return state;
     }
 
-    @Nullable InsetsSourceControl[] getControlsForDispatch(WindowState target) {
-        ArrayList<Integer> controlled = mWinControlTypeMap.get(target);
+    @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
+        ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
         if (controlled == null) {
             return null;
         }
         final int size = controlled.size();
         final InsetsSourceControl[] result = new InsetsSourceControl[size];
         for (int i = 0; i < size; i++) {
-            result[i] = mControllers.get(controlled.get(i)).getControl();
+            result[i] = mProviders.get(controlled.get(i)).getControl(target);
         }
         return result;
     }
@@ -103,8 +107,8 @@
     /**
      * @return The provider of a specific type.
      */
-    InsetsSourceProvider getSourceProvider(int type) {
-        return mControllers.computeIfAbsent(type,
+    InsetsSourceProvider getSourceProvider(@InternalInsetType int type) {
+        return mProviders.computeIfAbsent(type,
                 key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent));
     }
 
@@ -113,8 +117,8 @@
      */
     void onPostLayout() {
         mState.setDisplayFrame(mDisplayContent.getBounds());
-        for (int i = mControllers.size() - 1; i>= 0; i--) {
-            mControllers.valueAt(i).onPostLayout();
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            mProviders.valueAt(i).onPostLayout();
         }
         if (!mLastState.equals(mState)) {
             mLastState.set(mState, true /* copySources */);
@@ -126,7 +130,7 @@
         boolean changed = false;
         for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
-            final InsetsSourceProvider provider = mControllers.get(source.getType());
+            final InsetsSourceProvider provider = mProviders.get(source.getType());
             if (provider == null) {
                 continue;
             }
@@ -137,75 +141,116 @@
         }
     }
 
-    void onImeTargetChanged(@Nullable WindowState imeTarget) {
+    void onImeTargetChanged(@Nullable InsetsControlTarget imeTarget) {
         onControlChanged(TYPE_IME, imeTarget);
         notifyPendingInsetsControlChanged();
     }
 
     /**
-     * Called when the top opaque fullscreen window that is able to control the system bars changes.
+     * Called when the focused window that is able to control the system bars changes.
      *
-     * @param controllingWindow The window that is now able to control the system bars appearance
-     *                          and visibility.
+     * @param topControlling The target that is now able to control the top bar appearance
+     *                       and visibility.
+     * @param navControlling The target that is now able to control the nav bar appearance
+     *                       and visibility.
      */
-    void onBarControllingWindowChanged(@Nullable WindowState controllingWindow) {
-        // TODO: Apply policy that determines whether controllingWindow is able to control system
-        // bars
-
-        // TODO: Depending on the form factor, mapping is different
-        onControlChanged(TYPE_TOP_BAR, controllingWindow);
-        onControlChanged(TYPE_NAVIGATION_BAR, controllingWindow);
+    void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling,
+            @Nullable InsetsControlTarget navControlling) {
+        onControlChanged(TYPE_TOP_BAR, topControlling);
+        onControlChanged(TYPE_NAVIGATION_BAR, navControlling);
         notifyPendingInsetsControlChanged();
     }
 
-    void notifyControlRevoked(@NonNull WindowState previousControllingWin,
+    void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
-        removeFromControlMaps(previousControllingWin, provider.getSource().getType());
+        removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
+                false /* fake */);
     }
 
-    private void onControlChanged(int type, @Nullable WindowState win) {
-        final WindowState previous = mTypeWinControlMap.get(type);
-        if (win == previous) {
+    private void onControlChanged(@InternalInsetType int type,
+            @Nullable InsetsControlTarget target) {
+        final InsetsControlTarget previous = mTypeControlTargetMap.get(type);
+        if (target == previous) {
             return;
         }
-        final InsetsSourceProvider controller = getSourceProvider(type);
-        if (controller == null) {
+        final InsetsSourceProvider provider = getSourceProvider(type);
+        if (provider == null) {
             return;
         }
-        if (!controller.isControllable()) {
+        if (!provider.isControllable()) {
             return;
         }
-        controller.updateControlForTarget(win, false /* force */);
+        provider.updateControlForTarget(target, false /* force */);
         if (previous != null) {
-            removeFromControlMaps(previous, type);
+            removeFromControlMaps(previous, type, false /* fake */);
             mPendingControlChanged.add(previous);
         }
-        if (win != null) {
-            addToControlMaps(win, type);
-            mPendingControlChanged.add(win);
+        if (target != null) {
+            addToControlMaps(target, type, false /* fake */);
+            mPendingControlChanged.add(target);
         }
     }
 
-    private void removeFromControlMaps(@NonNull WindowState win, int type) {
-        final ArrayList<Integer> array = mWinControlTypeMap.get(win);
+    /**
+     * The fake target saved here will be used to pretend to the app that it's still under control
+     * of the bars while it's not really, but we still need to find out the apps intentions around
+     * showing/hiding. For example, when the transient bars are showing, and the fake target
+     * requests to show system bars, the transient state will be aborted.
+     */
+    void onControlFakeTargetChanged(@InternalInsetType  int type,
+            @Nullable InsetsControlTarget fakeTarget) {
+        if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
+            return;
+        }
+        final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type);
+        if (fakeTarget == previous) {
+            return;
+        }
+        final InsetsSourceProvider provider = mProviders.get(type);
+        if (provider == null) {
+            return;
+        }
+        provider.updateControlForFakeTarget(fakeTarget);
+        if (previous != null) {
+            removeFromControlMaps(previous, type, true /* fake */);
+            mPendingControlChanged.add(previous);
+        }
+        if (fakeTarget != null) {
+            addToControlMaps(fakeTarget, type, true /* fake */);
+            mPendingControlChanged.add(fakeTarget);
+        }
+    }
+
+    private void removeFromControlMaps(@NonNull InsetsControlTarget target,
+            @InternalInsetType int type, boolean fake) {
+        final ArrayList<Integer> array = mControlTargetTypeMap.get(target);
         if (array == null) {
             return;
         }
         array.remove((Integer) type);
         if (array.isEmpty()) {
-            mWinControlTypeMap.remove(win);
+            mControlTargetTypeMap.remove(target);
         }
-        mTypeWinControlMap.remove(type);
+        if (fake) {
+            mTypeFakeControlTargetMap.remove(type);
+        } else {
+            mTypeControlTargetMap.remove(type);
+        }
     }
 
-    private void addToControlMaps(@NonNull WindowState win, int type) {
-        final ArrayList<Integer> array = mWinControlTypeMap.computeIfAbsent(win,
+    private void addToControlMaps(@NonNull InsetsControlTarget target,
+            @InternalInsetType int type, boolean fake) {
+        final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target,
                 key -> new ArrayList<>());
         array.add(type);
-        mTypeWinControlMap.put(type, win);
+        if (fake) {
+            mTypeFakeControlTargetMap.put(type, target);
+        } else {
+            mTypeControlTargetMap.put(type, target);
+        }
     }
 
-    void notifyControlChanged(WindowState target) {
+    void notifyControlChanged(InsetsControlTarget target) {
         mPendingControlChanged.add(target);
         notifyPendingInsetsControlChanged();
     }
@@ -216,8 +261,8 @@
         }
         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
-                final WindowState controllingWin = mPendingControlChanged.valueAt(i);
-                controllingWin.notifyInsetsControlChanged();
+                final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
+                controlTarget.notifyInsetsControlChanged();
             }
             mPendingControlChanged.clear();
         });
@@ -231,10 +276,10 @@
         pw.println(prefix + "WindowInsetsStateController");
         mState.dump(prefix + "  ", pw);
         pw.println(prefix + "  " + "Control map:");
-        for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
+        for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
             pw.print(prefix + "  ");
-            pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
-                    + mTypeWinControlMap.valueAt(i));
+            pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
+                    + mTypeControlTargetMap.valueAt(i));
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 422b6e5..2b5eb3a 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -71,7 +71,6 @@
     private boolean mAodShowing;
     private boolean mKeyguardGoingAway;
     private boolean mDismissalRequested;
-    private int[] mSecondaryDisplayIdsShowing;
     private int mBeforeUnoccludeTransit;
     private int mVisibilityTransactionDepth;
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
@@ -182,7 +181,7 @@
             return;
         }
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             setKeyguardGoingAway(true);
             EventLog.writeEvent(EventLogTags.AM_SET_KEYGUARD_SHOWN,
@@ -204,7 +203,7 @@
             mWindowManager.executeAppTransition();
         } finally {
             Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
 
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -328,9 +327,9 @@
             return;
         }
 
-        mWindowManager.onKeyguardOccludedChanged(isDisplayOccluded(DEFAULT_DISPLAY));
+        mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY));
         if (isKeyguardLocked()) {
-            mWindowManager.deferSurfaceLayout();
+            mService.deferWindowLayout();
             try {
                 mRootActivityContainer.getDefaultDisplay().mDisplayContent
                         .prepareAppTransition(resolveOccludeTransit(),
@@ -340,7 +339,7 @@
                 mRootActivityContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
                 mWindowManager.executeAppTransition();
             } finally {
-                mWindowManager.continueSurfaceLayout();
+                mService.continueWindowLayout();
             }
         }
         dismissDockedStackIfNeeded();
@@ -381,7 +380,7 @@
      * @return true if Keyguard can be currently dismissed without entering credentials.
      */
     boolean canDismissKeyguard() {
-        return mWindowManager.isKeyguardTrusted()
+        return mWindowManager.mPolicy.isKeyguardTrustedLw()
                 || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
     }
 
@@ -516,7 +515,8 @@
             }
             // TODO(b/123372519): isShowingDream can only works on default display.
             if (mDisplayId == DEFAULT_DISPLAY) {
-                mOccluded |= controller.mWindowManager.isShowingDream();
+                mOccluded |= mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent
+                        .getDisplayPolicy().isShowingDreamLw();
             }
 
             if (lastOccluded != mOccluded) {
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 59c02f7..6de48d1 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -133,7 +133,7 @@
             return false;
         }
 
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (mTmpParams.hasPreferredDisplay()
@@ -152,7 +152,7 @@
 
             if (task.getStack().inFreeformWindowingMode()) {
                 // Only set bounds if it's in freeform mode.
-                task.updateOverrideConfiguration(mTmpParams.mBounds);
+                task.setBounds(mTmpParams.mBounds);
                 return true;
             }
 
@@ -161,7 +161,7 @@
             task.setLastNonFullscreenBounds(mTmpParams.mBounds);
             return false;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index bb035d5..1bd2493 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -41,7 +41,8 @@
     private static final Rect EMPTY_RECT = new Rect();
     private static final Point ZERO_POINT = new Point(0, 0);
 
-    private final Supplier<SurfaceControl.Builder> mFactory;
+    private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
+    private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
     private final Rect mOuter = new Rect();
     private final Rect mInner = new Rect();
     private final LetterboxSurface mTop = new LetterboxSurface("top");
@@ -55,8 +56,10 @@
      *
      * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s
      */
-    public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) {
-        mFactory = surfaceControlFactory;
+    public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            Supplier<SurfaceControl.Transaction> transactionFactory) {
+        mSurfaceControlFactory = surfaceControlFactory;
+        mTransactionFactory = transactionFactory;
     }
 
     /**
@@ -245,7 +248,7 @@
         }
 
         private void createSurface() {
-            mSurface = mFactory.get().setName("Letterbox - " + mType)
+            mSurface = mSurfaceControlFactory.get().setName("Letterbox - " + mType)
                     .setFlags(HIDDEN).setColorLayer().build();
             mSurface.setLayer(-1);
             mSurface.setColor(new float[]{0, 0, 0});
@@ -261,7 +264,7 @@
 
         public void remove() {
             if (mSurface != null) {
-                new SurfaceControl.Transaction().remove(mSurface).apply();
+                mTransactionFactory.get().remove(mSurface).apply();
                 mSurface = null;
             }
             if (mInputInterceptor != null) {
diff --git a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
index 77a024c..e67cb6fc 100644
--- a/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
@@ -49,11 +49,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return mSpec.getBackgroundColor();
-    }
-
-    @Override
     public void startAnimation(SurfaceControl animationLeash, Transaction t,
             OnAnimationFinishedCallback finishCallback) {
         mAnimator.startAnimation(mSpec, animationLeash, t,
@@ -100,13 +95,6 @@
         }
 
         /**
-         * @see AnimationAdapter#getBackgroundColor
-         */
-        default int getBackgroundColor() {
-            return 0;
-        }
-
-        /**
          * @see AnimationAdapter#getStatusBarTransitionsStartTime
          */
         default long calculateStatusBarTransitionStartTime() {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index ef0049b..8e57fec 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
@@ -50,7 +51,6 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -74,7 +74,7 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "PinnedStackController" : TAG_WM;
 
-    public static final float INVALID_SNAP_FRACTION = -1f;
+    private static final float INVALID_SNAP_FRACTION = -1f;
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
     private final Handler mHandler = UiThread.getHandler();
@@ -106,9 +106,6 @@
     private int mDefaultStackGravity;
     private float mDefaultAspectRatio;
     private Point mScreenEdgeInsets;
-    private int mCurrentMinSize;
-    private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
-    private WeakReference<AppWindowToken> mLastPipActivity = null;
 
     // The aspect ratio bounds of the PIP.
     private float mMinAspectRatio;
@@ -118,7 +115,6 @@
     private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
     private final Rect mTmpInsets = new Rect();
     private final Rect mTmpRect = new Rect();
-    private final Rect mTmpAnimatingBoundsRect = new Rect();
     private final Point mTmpDisplaySize = new Point();
 
 
@@ -136,18 +132,21 @@
         }
 
         @Override
-        public void setMinEdgeSize(int minEdgeSize) {
-            mHandler.post(() -> {
-                mCurrentMinSize = Math.max(mDefaultMinSize, minEdgeSize);
-            });
-        }
-
-        @Override
         public int getDisplayRotation() {
             synchronized (mService.mGlobalLock) {
                 return mDisplayInfo.rotation;
             }
         }
+
+        @Override
+        public void startAnimation(Rect destinationBounds, Rect sourceRectHint,
+                int animationDuration) {
+            synchronized (mService.mGlobalLock) {
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+                pinnedStack.animateResizePinnedStack(destinationBounds,
+                        sourceRectHint, animationDuration, true /* fromFullscreen */);
+            }
+        }
     }
 
     /**
@@ -188,7 +187,6 @@
         final Resources res = mService.mContext.getResources();
         mDefaultMinSize = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.default_minimal_size_pip_resizable_task);
-        mCurrentMinSize = mDefaultMinSize;
         mDefaultAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio);
         final String screenEdgeInsetsDpString = res.getString(
@@ -216,6 +214,7 @@
             listener.asBinder().linkToDeath(mPinnedStackListenerDeathHandler, 0);
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
+            notifyDisplayInfoChanged(mDisplayInfo);
             notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
             notifyShelfVisibilityChanged(mIsShelfShowing, mShelfHeight);
             // The movement bounds notification needs to be sent before the minimized state, since
@@ -238,58 +237,34 @@
     }
 
     /**
-     * Returns the current bounds (or the default bounds if there are no current bounds) with the
-     * specified aspect ratio.
-     */
-    Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio,
-            boolean useCurrentMinEdgeSize) {
-        // Save the snap fraction, calculate the aspect ratio based on screen size
-        final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
-                getMovementBounds(stackBounds));
-
-        final int minEdgeSize = useCurrentMinEdgeSize ? mCurrentMinSize : mDefaultMinSize;
-        final Size size = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, minEdgeSize,
-                mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
-        final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
-        final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f);
-        stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight());
-        mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
-        if (mIsMinimized) {
-            applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds));
-        }
-        return stackBounds;
-    }
-
-    /**
      * Saves the current snap fraction for re-entry of the current activity into PiP.
      */
-    void saveReentrySnapFraction(final AppWindowToken token, final Rect stackBounds) {
-        mReentrySnapFraction = getSnapFraction(stackBounds);
-        mLastPipActivity = new WeakReference<>(token);
+    void saveReentrySnapFraction(final ComponentName componentName, final Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onSaveReentrySnapFraction(componentName, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering save reentry fraction event.", e);
+        }
     }
 
     /**
      * Resets the last saved snap fraction so that the default bounds will be returned.
      */
-    void resetReentrySnapFraction(AppWindowToken token) {
-        if (mLastPipActivity != null && mLastPipActivity.get() == token) {
-            mReentrySnapFraction = INVALID_SNAP_FRACTION;
-            mLastPipActivity = null;
+    void resetReentrySnapFraction(ComponentName componentName) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onResetReentrySnapFraction(componentName);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
         }
     }
 
     /**
-     * @return the default bounds to show the PIP when there is no active PIP.
-     */
-    Rect getDefaultOrLastSavedBounds() {
-        return getDefaultBounds(mReentrySnapFraction);
-    }
-
-    /**
      * @return the default bounds to show the PIP, if a {@param snapFraction} is provided, then it
      * will apply the default bounds to the provided snap fraction.
      */
-    Rect getDefaultBounds(float snapFraction) {
+    private Rect getDefaultBounds(float snapFraction) {
         synchronized (mService.mGlobalLock) {
             final Rect insetBounds = new Rect();
             getInsetBounds(insetBounds);
@@ -311,13 +286,18 @@
         }
     }
 
+    private void setDisplayInfo(DisplayInfo displayInfo) {
+        mDisplayInfo.copyFrom(displayInfo);
+        notifyDisplayInfoChanged(mDisplayInfo);
+    }
+
     /**
      * In the case where the display rotation is changed but there is no stack, we can't depend on
      * onTaskStackBoundsChanged() to be called.  But we still should update our known display info
      * with the new state so that we can update SystemUI.
      */
     synchronized void onDisplayInfoChanged(DisplayInfo displayInfo) {
-        mDisplayInfo.copyFrom(displayInfo);
+        setDisplayInfo(displayInfo);
         notifyMovementBoundsChanged(false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
     }
 
@@ -335,7 +315,7 @@
             } else if (targetBounds.isEmpty()) {
                 // The stack is null, we are just initializing the stack, so just store the display
                 // info and ignore
-                mDisplayInfo.copyFrom(displayInfo);
+                setDisplayInfo(displayInfo);
                 outBounds.setEmpty();
                 return false;
             }
@@ -345,7 +325,8 @@
 
             // Calculate the snap fraction of the current stack along the old movement bounds
             final float snapFraction = getSnapFraction(postChangeStackBounds);
-            mDisplayInfo.copyFrom(displayInfo);
+
+            setDisplayInfo(displayInfo);
 
             // Calculate the stack bounds in the new orientation to the same same fraction along the
             // rotated movement bounds.
@@ -406,8 +387,11 @@
     void setAspectRatio(float aspectRatio) {
         if (Float.compare(mAspectRatio, aspectRatio) != 0) {
             mAspectRatio = aspectRatio;
+            notifyAspectRatioChanged(aspectRatio);
             notifyMovementBoundsChanged(false /* fromImeAdjustment */,
                     false /* fromShelfAdjustment */);
+            notifyPrepareAnimation(null /* sourceHintRect */, aspectRatio,
+                    null /* stackBounds */);
         }
     }
 
@@ -429,6 +413,10 @@
         notifyActionsChanged(mActions);
     }
 
+    void prepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        notifyPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+    }
+
     private boolean isSameDimensionAndRotation(@NonNull DisplayInfo display1,
             @NonNull DisplayInfo display2) {
         Preconditions.checkNotNull(display1);
@@ -461,6 +449,15 @@
         }
     }
 
+    private void notifyAspectRatioChanged(float aspectRatio) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onAspectRatioChanged(aspectRatio);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering aspect ratio changed event.", e);
+        }
+    }
+
     /**
      * Notifies listeners that the PIP minimized state has changed.
      */
@@ -497,23 +494,13 @@
                 return;
             }
             try {
-                final Rect insetBounds = new Rect();
-                getInsetBounds(insetBounds);
-                final Rect normalBounds = getDefaultBounds(INVALID_SNAP_FRACTION);
-                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                            false /* useCurrentMinEdgeSize */);
-                }
-                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final Rect animatingBounds = new Rect();
                 final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                } else {
-                    animatingBounds.set(normalBounds);
                 }
-                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                        animatingBounds, fromImeAdjustment, fromShelfAdjustment,
-                        mDisplayInfo.rotation);
+                mPinnedStackListener.onMovementBoundsChanged(animatingBounds,
+                        fromImeAdjustment, fromShelfAdjustment);
             } catch (RemoteException e) {
                 Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
@@ -521,6 +508,30 @@
     }
 
     /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyDisplayInfoChanged(DisplayInfo displayInfo) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onDisplayInfoChanged(displayInfo);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering DisplayInfo changed event.", e);
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP animation is about to happen.
+     */
+    private void notifyPrepareAnimation(Rect sourceRectHint, float aspectRatio, Rect stackBounds) {
+        if (mPinnedStackListener == null) return;
+        try {
+            mPinnedStackListener.onPrepareAnimation(sourceRectHint, aspectRatio, stackBounds);
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Error delivering prepare animation event.", e);
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -604,7 +615,6 @@
         pw.println(prefix + "  mImeHeight=" + mImeHeight);
         pw.println(prefix + "  mIsShelfShowing=" + mIsShelfShowing);
         pw.println(prefix + "  mShelfHeight=" + mShelfHeight);
-        pw.println(prefix + "  mReentrySnapFraction=" + mReentrySnapFraction);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
         pw.println(prefix + "  mAspectRatio=" + mAspectRatio);
         pw.println(prefix + "  mMinAspectRatio=" + mMinAspectRatio);
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
index 8d08aa3..6b8144c 100644
--- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -28,13 +28,11 @@
 import java.util.ArrayList;
 
 public class PointerEventDispatcher extends InputEventReceiver {
-    private final InputChannel mInputChannel;
     private final ArrayList<PointerEventListener> mListeners = new ArrayList<>();
     private PointerEventListener[] mListenersArray = new PointerEventListener[0];
 
     public PointerEventDispatcher(InputChannel inputChannel) {
         super(inputChannel, UiThread.getHandler().getLooper());
-        mInputChannel = inputChannel;
     }
 
     @Override
@@ -94,7 +92,6 @@
     @Override
     public void dispose() {
         super.dispose();
-        mInputChannel.dispose();
         synchronized (mListeners) {
             mListeners.clear();
             mListenersArray = null;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 1a8944a..5cabbd9 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -196,7 +196,7 @@
             mCaller.setRunningRecentsAnimation(true);
         }
 
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
@@ -260,7 +260,7 @@
             Slog.e(TAG, "Failed to start recents activity", e);
             throw e;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
         }
     }
@@ -297,7 +297,7 @@
             mWindowManager.inSurfaceTransaction(() -> {
                 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
                         "RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
-                mWindowManager.deferSurfaceLayout();
+                mService.deferWindowLayout();
                 try {
                     mWindowManager.cleanupRecentsAnimation(reorderMode);
 
@@ -387,7 +387,7 @@
                     Slog.e(TAG, "Failed to clean up recents activity", e);
                     throw e;
                 } finally {
-                    mWindowManager.continueSurfaceLayout();
+                    mService.continueWindowLayout();
                     Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
                 }
             });
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 8752f37..795a2ca 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -93,6 +93,8 @@
     private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final int mDisplayId;
     private final Runnable mFailsafeRunnable = () ->
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
@@ -434,6 +436,13 @@
         mPendingAnimations.remove(taskAdapter);
     }
 
+    @VisibleForTesting
+    void removeWallpaperAnimation(WallpaperAnimationAdapter wallpaperAdapter) {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "removeWallpaperAnimation()");
+        wallpaperAdapter.getLeashFinishedCallback().onAnimationFinished(wallpaperAdapter);
+        mPendingWallpaperAnimations.remove(wallpaperAdapter);
+    }
+
     void startAnimation() {
         if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "startAnimation(): mPendingStart=" + mPendingStart
                 + " mCanceled=" + mCanceled);
@@ -442,25 +451,18 @@
             return;
         }
         try {
-            final ArrayList<RemoteAnimationTarget> appAnimations = new ArrayList<>();
-            for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
-                final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
-                final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationApp();
-                if (target != null) {
-                    appAnimations.add(target);
-                } else {
-                    removeAnimation(taskAdapter);
-                }
-            }
+            // Create the app targets
+            final RemoteAnimationTarget[] appTargets = createAppAnimations();
 
             // Skip the animation if there is nothing to animate
-            if (appAnimations.isEmpty()) {
+            if (appTargets.length == 0) {
                 cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "startAnimation-noAppWindows");
                 return;
             }
 
-            final RemoteAnimationTarget[] appTargets = appAnimations.toArray(
-                    new RemoteAnimationTarget[appAnimations.size()]);
+            // Create the wallpaper targets
+            final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
+
             mPendingStart = false;
 
             // Perform layout if it was scheduled before to make sure that we get correct content
@@ -479,7 +481,8 @@
                 mService.getStableInsets(mDisplayId, mTmpRect);
                 contentInsets = mTmpRect;
             }
-            mRunner.onAnimationStart(mController, appTargets, contentInsets, minimizedHomeBounds);
+            mRunner.onAnimationStart(mController, appTargets, wallpaperTargets, contentInsets,
+                    minimizedHomeBounds);
             if (DEBUG_RECENTS_ANIMATIONS) {
                 Slog.d(TAG, "startAnimation(): Notify animation start:");
                 for (int i = 0; i < mPendingAnimations.size(); i++) {
@@ -495,6 +498,32 @@
         mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
     }
 
+    private RemoteAnimationTarget[] createAppAnimations() {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i);
+            final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget();
+            if (target != null) {
+                targets.add(target);
+            } else {
+                removeAnimation(taskAdapter);
+            }
+        }
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService, 0L, 0L,
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the recents
+                        // animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     void cancelAnimation(@ReorderMode int reorderMode, String reason) {
         cancelAnimation(reorderMode, false /*screenshot */, reason);
     }
@@ -592,7 +621,7 @@
             return null;
         }
 
-        final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(task,
+        final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable(mService.mSurfaceControlFactory, task,
                 new SurfaceControl.ScreenshotGraphicBuffer(taskSnapshot.getSnapshot(),
                         taskSnapshot.getColorSpace(), false /* containsSecureLayers */));
         mRecentScreenshotAnimator = new SurfaceAnimator(
@@ -619,6 +648,11 @@
             removeAnimation(taskAdapter);
         }
 
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i);
+            removeWallpaperAnimation(wallpaperAdapter);
+        }
+
         // Clear any pending failsafe runnables
         mService.mH.removeCallbacks(mFailsafeRunnable);
         mDisplayContent.mAppTransition.unregisterListener(mAppTransitionListener);
@@ -747,6 +781,15 @@
         return false;
     }
 
+    boolean isAnimatingWallpaper(WallpaperWindowToken token) {
+        for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+            if (token == mPendingWallpaperAnimations.get(i).getToken()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean isAnimatingApp(AppWindowToken appToken) {
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final Task task = mPendingAnimations.get(i).mTask;
@@ -760,6 +803,11 @@
         return false;
     }
 
+    boolean shouldIgnoreForAccessibility(WindowState windowState) {
+        final Task task = windowState.getTask();
+        return task != null && isAnimatingTask(task) && !isTargetApp(windowState.mAppToken);
+    }
+
     @VisibleForTesting
     class TaskAnimationAdapter implements AnimationAdapter {
 
@@ -779,7 +827,7 @@
             mBounds.set(container.getDisplayedBounds());
         }
 
-        RemoteAnimationTarget createRemoteAnimationApp() {
+        RemoteAnimationTarget createRemoteAnimationTarget() {
             final AppWindowToken topApp = mTask.getTopVisibleAppToken();
             final WindowState mainWindow = topApp != null
                     ? topApp.findMainWindow()
@@ -806,11 +854,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             // Restore z-layering, position and stack crop until client has a chance to modify it.
@@ -825,6 +868,7 @@
 
         @Override
         public void onAnimationCancelled(SurfaceControl animationLeash) {
+            // Cancel the animation immediately if any single task animator is canceled
             cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "taskAnimationAdapterCanceled");
         }
 
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 7448e00..87bda4a 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -58,6 +58,8 @@
     private final WindowManagerService mService;
     private final RemoteAnimationAdapter mRemoteAnimationAdapter;
     private final ArrayList<RemoteAnimationRecord> mPendingAnimations = new ArrayList<>();
+    private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
+            new ArrayList<>();
     private final Rect mTmpRect = new Rect();
     private final Handler mHandler;
     private final Runnable mTimeoutRunnable = () -> cancelAnimation("timeoutRunnable");
@@ -110,16 +112,21 @@
                 (long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));
         mFinishedCallback = new FinishedCallback(this);
 
-        final RemoteAnimationTarget[] animations = createAnimations();
-        if (animations.length == 0) {
+        // Create the app targets
+        final RemoteAnimationTarget[] appTargets = createAppAnimations();
+        if (appTargets.length == 0) {
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "goodToGo(): No apps to animate");
             onAnimationFinished();
             return;
         }
+
+        // Create the remote wallpaper animation targets (if any)
+        final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();
         mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
             try {
                 linkToDeathOfRunner();
-                mRemoteAnimationAdapter.getRunner().onAnimationStart(animations, mFinishedCallback);
+                mRemoteAnimationAdapter.getRunner().onAnimationStart(appTargets, wallpaperTargets,
+                        mFinishedCallback);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to start remote animation", e);
                 onAnimationFinished();
@@ -155,8 +162,8 @@
         Slog.i(TAG, sw.toString());
     }
 
-    private RemoteAnimationTarget[] createAnimations() {
-        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAnimations()");
+    private RemoteAnimationTarget[] createAppAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createAppAnimations()");
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final RemoteAnimationRecord wrappers = mPendingAnimations.get(i);
@@ -186,6 +193,19 @@
         return targets.toArray(new RemoteAnimationTarget[targets.size()]);
     }
 
+    private RemoteAnimationTarget[] createWallpaperAnimations() {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "createWallpaperAnimations()");
+        return WallpaperAnimationAdapter.startWallpaperAnimations(mService,
+                mRemoteAnimationAdapter.getDuration(),
+                mRemoteAnimationAdapter.getStatusBarTransitionDelay(),
+                adapter -> {
+                    synchronized (mService.mGlobalLock) {
+                        // If the wallpaper animation is canceled, continue with the app animation
+                        mPendingWallpaperAnimations.remove(adapter);
+                    }
+                }, mPendingWallpaperAnimations);
+    }
+
     private void onAnimationFinished() {
         if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationFinished(): mPendingAnimations="
                 + mPendingAnimations.size());
@@ -207,7 +227,15 @@
                         adapters.mThumbnailAdapter.mCapturedFinishCallback
                                 .onAnimationFinished(adapters.mThumbnailAdapter);
                     }
-                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\t" + adapters.mAppWindowToken);
+                    mPendingAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\tapp=" + adapters.mAppWindowToken);
+                }
+
+                for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
+                    final WallpaperAnimationAdapter adapter = mPendingWallpaperAnimations.get(i);
+                    adapter.getLeashFinishedCallback().onAnimationFinished(adapter);
+                    mPendingWallpaperAnimations.remove(i);
+                    if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "\twallpaper=" + adapter.getToken());
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Failed to finish remote animation", e);
@@ -390,11 +418,6 @@
         }
 
         @Override
-        public int getBackgroundColor() {
-            return 0;
-        }
-
-        @Override
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 OnAnimationFinishedCallback finishCallback) {
             if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
@@ -424,10 +447,7 @@
                 mPendingAnimations.remove(mRecord);
             }
             if (mPendingAnimations.isEmpty()) {
-                mHandler.removeCallbacks(mTimeoutRunnable);
-                releaseFinishedCallback();
-                invokeAnimationCancelled();
-                setRunningRemoteAnimation(false);
+                cancelAnimation("allAppAnimationsCanceled");
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index eb5d096..734f224 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -27,12 +27,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
@@ -93,7 +91,6 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
@@ -196,6 +193,9 @@
     /** Set when a power hint has started, but not ended. */
     private boolean mPowerHintSent;
 
+    /** Used to keep ensureActivitiesVisible() from being entered recursively. */
+    private boolean mInEnsureActivitiesVisible = false;
+
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
     int mDefaultMinSizeOfResizeableTaskDp = -1;
@@ -808,8 +808,14 @@
      */
     void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
-        mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
+        if (mInEnsureActivitiesVisible) {
+            // Don't do recursive work.
+            return;
+        }
+        mInEnsureActivitiesVisible = true;
+
         try {
+            mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -818,6 +824,7 @@
             }
         } finally {
             mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
+            mInEnsureActivitiesVisible = false;
         }
     }
 
@@ -881,48 +888,6 @@
         }
     }
 
-    void resizeStack(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
-            Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,
-            boolean deferResume) {
-
-        if (stack.inSplitScreenPrimaryWindowingMode()) {
-            mStackSupervisor.resizeDockedStackLocked(bounds, tempTaskBounds,
-                    tempTaskInsetBounds, null, null, preserveWindows, deferResume);
-            return;
-        }
-
-        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();
-        if (!allowResizeInDockedMode
-                && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
-            // If the docked stack exists, don't resize non-floating stacks independently of the
-            // size computed from the docked stack size (otherwise they will be out of sync)
-            return;
-        }
-
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
-        mWindowManager.deferSurfaceLayout();
-        try {
-            if (stack.affectedBySplitScreenResize()) {
-                if (bounds == null && stack.inSplitScreenWindowingMode()) {
-                    // null bounds = fullscreen windowing mode...at least for now.
-                    stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-                } else if (splitScreenActive) {
-                    // If we are in split-screen mode and this stack support split-screen, then
-                    // it should be split-screen secondary mode. i.e. adjacent to the docked stack.
-                    stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-                }
-            }
-            stack.resize(bounds, tempTaskBounds, tempTaskInsetBounds);
-            if (!deferResume) {
-                stack.ensureVisibleActivitiesConfigurationLocked(
-                        stack.topRunningActivityLocked(), preserveWindows);
-            }
-        } finally {
-            mWindowManager.continueSurfaceLayout();
-            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-        }
-    }
-
     /**
      * Move stack with all its existing content to specified display.
      * @param stackId Id of stack to move.
@@ -990,8 +955,7 @@
 
     void moveActivityToPinnedStack(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
             String reason) {
-
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         final ActivityDisplay display = r.getActivityStack().getDisplay();
         ActivityStack stack = display.getPinnedStack();
@@ -1005,18 +969,13 @@
         // Need to make sure the pinned stack exist so we can resize it below...
         stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
-        // Calculate the target bounds here before the task is reparented back into pinned windowing
-        // mode (which will reset the saved bounds)
-        final Rect destBounds = stack.getDefaultPictureInPictureBounds(aspectRatio);
-
         try {
             final TaskRecord task = r.getTaskRecord();
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
-            resizeStack(stack, task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
-                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
-                    true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            stack.resize(task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
+                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
 
             if (task.mActivities.size() == 1) {
                 // Defer resume until below, and do not schedule PiP changes until we animate below
@@ -1045,12 +1004,17 @@
             // to the pinned stack
             r.supportsEnterPipOnTaskSwitch = false;
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
-        stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
-                true /* fromFullscreen */);
+        // Notify the pinned stack controller to prepare the PiP animation, expect callback
+        // delivered from SystemUI to WM to start the animation.
+        final PinnedStackController pinnedStackController =
+                display.mDisplayContent.getPinnedStackController();
+        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                null /* stackBounds */);
 
+        // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
         // stack.  This MUST run after the animation above is scheduled to ensure that the windows
         // drawn signal is scheduled after the bounds animation start call on the bounds animator
@@ -1661,7 +1625,8 @@
 
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
-        return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */);
+        return getLaunchStack(r, options, candidateTask, onTop, null /* launchParams */,
+                -1 /* no realCallingPid */, -1 /* no realCallingUid */);
     }
 
     /**
@@ -1670,13 +1635,16 @@
      * @param r The activity we are trying to launch. Can be null.
      * @param options The activity options used to the launch. Can be null.
      * @param candidateTask The possible task the activity might be launched in. Can be null.
-     * @params launchParams The resolved launch params to use.
+     * @param launchParams The resolved launch params to use.
+     * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
+     * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
      *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
     <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
-            @Nullable LaunchParamsController.LaunchParams launchParams) {
+            @Nullable LaunchParamsController.LaunchParams launchParams, int realCallingPid,
+            int realCallingUid) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
         //Rect bounds = null;
@@ -1707,7 +1675,15 @@
         if (launchParams != null && launchParams.mPreferredDisplayId != INVALID_DISPLAY) {
             displayId = launchParams.mPreferredDisplayId;
         }
-        if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
+        final boolean canLaunchOnDisplayFromStartRequest =
+                realCallingPid != 0 && realCallingUid > 0 && r != null
+                        && mStackSupervisor.canPlaceEntityOnDisplay(displayId, realCallingPid,
+                        realCallingUid, r.info);
+        // Checking if the activity's launch caller, or the realCallerId of the activity from
+        // start request (i.e. entity that invokes PendingIntent) is allowed to launch on the
+        // display.
+        if (displayId != INVALID_DISPLAY && (canLaunchOnDisplay(r, displayId)
+                || canLaunchOnDisplayFromStartRequest)) {
             if (r != null) {
                 stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options,
                         launchParams);
@@ -2106,7 +2082,7 @@
      * @param userId user handle for the locked managed profile.
      */
     void lockAllProfileTasks(@UserIdInt int userId) {
-        mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         try {
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
@@ -2127,7 +2103,7 @@
                 }
             }
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -2260,9 +2236,9 @@
     void getRunningTasks(int maxNum, List<ActivityManager.RunningTaskInfo> list,
             @WindowConfiguration.ActivityType int ignoreActivityType,
             @WindowConfiguration.WindowingMode int ignoreWindowingMode, int callingUid,
-            boolean allowed) {
+            boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
         mStackSupervisor.getRunningTasks().getTasks(maxNum, list, ignoreActivityType,
-                ignoreWindowingMode, mActivityDisplays, callingUid, allowed);
+                ignoreWindowingMode, mActivityDisplays, callingUid, allowed, crossUser, profileIds);
     }
 
     void sendPowerHintForLaunchStartIfNeeded(boolean forceSend, ActivityRecord targetActivity) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 968d02b..4365d03 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -134,7 +134,7 @@
 
     // Only a separate transaction until we separate the apply surface changes
     // transaction from the global transaction.
-    private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mDisplayTransaction;
 
     private final Consumer<WindowState> mCloseSystemDialogsConsumer = w -> {
         if (w.mHasSurface) {
@@ -154,6 +154,7 @@
 
     RootWindowContainer(WindowManagerService service) {
         super(service);
+        mDisplayTransaction = service.mTransactionFactory.get();
         mHandler = new MyHandler(service.mH.getLooper());
     }
 
@@ -471,8 +472,7 @@
 
         final int count = mChildren.size();
         for (int i = 0; i < count; ++i) {
-            final DisplayContent dc = mChildren.get(i);
-            final int pendingChanges = animator.getPendingLayoutChanges(dc.getDisplayId());
+            final int pendingChanges = mChildren.get(i).pendingLayoutChanges;
             if ((pendingChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
                 animator.mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
             }
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 3bf437d..81a8547 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -19,6 +19,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.util.ArraySet;
 
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -40,7 +41,7 @@
 
     void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
             @WindowingMode int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
-            int callingUid, boolean allowed) {
+            int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
         // Return early if there are no tasks to fetch
         if (maxNum <= 0) {
             return;
@@ -55,7 +56,7 @@
                 final ActivityStack stack = display.getChildAt(stackNdx);
                 mTmpStackTasks.clear();
                 stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
-                        callingUid, allowed);
+                        callingUid, allowed, crossUser, profileIds);
                 mTmpSortedSet.addAll(mTmpStackTasks);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 82dde0d..cbaf098 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -127,7 +127,7 @@
         mOriginalWidth = originalWidth;
         mOriginalHeight = originalHeight;
 
-        final SurfaceControl.Transaction t = mService.mTransactionFactory.make();
+        final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
         try {
             mSurfaceControl = displayContent.makeOverlay()
                     .setName("ScreenshotSurface")
@@ -137,13 +137,13 @@
 
             // In case display bounds change, screenshot buffer and surface may mismatch so set a
             // scaling mode.
-            SurfaceControl.Transaction t2 = mService.mTransactionFactory.make();
+            SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
             t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW);
             t2.apply(true /* sync */);
 
             // Capture a screenshot into the surface we just created.
             final int displayId = display.getDisplayId();
-            final Surface surface = mService.mSurfaceFactory.make();
+            final Surface surface = mService.mSurfaceFactory.get();
             surface.copyFrom(mSurfaceControl);
             SurfaceControl.ScreenshotGraphicBuffer gb =
                     mService.mDisplayManagerInternal.screenshot(displayId);
@@ -427,7 +427,7 @@
                 Slog.i(TAG_WM,
                         "  FREEZE " + mSurfaceControl + ": DESTROY");
             }
-            mService.mTransactionFactory.make().remove(mSurfaceControl).apply();
+            mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
             mSurfaceControl = null;
         }
         if (mExitingBlackFrame != null) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 4a76042..72bb355 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -24,6 +24,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
 
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -227,8 +228,7 @@
     @Override
     public void finishDrawing(IWindow window,
             @Nullable SurfaceControl.Transaction postDrawTransaction) {
-        if (WindowManagerService.localLOGV) Slog.v(
-            TAG_WM, "IWindow finishDrawing called for " + window);
+        if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
         mService.finishDrawingWindow(this, window, postDrawTransaction);
     }
 
@@ -474,8 +474,9 @@
         mPackageName = packageName;
         mRelayoutTag = "relayoutWindow: " + mPackageName;
         if (mSurfaceSession == null) {
-            if (WindowManagerService.localLOGV) Slog.v(
-                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+            if (DEBUG) {
+                Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession");
+            }
             mSurfaceSession = new SurfaceSession();
             if (SHOW_TRANSACTIONS) Slog.i(
                     TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
@@ -565,8 +566,10 @@
             return;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG_WM, "Last window removed from " + this
-                + ", destroying " + mSurfaceSession);
+        if (DEBUG) {
+            Slog.v(TAG_WM, "Last window removed from " + this
+                    + ", destroying " + mSurfaceSession);
+        }
         if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  KILL SURFACE SESSION " + mSurfaceSession);
         try {
             mSurfaceSession.kill();
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index 82f2ad8..9e5d9ca 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -27,17 +27,20 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 class StrictModeFlash {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "StrictModeFlash" : TAG_WM;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
     private final int mThickness = 20;
 
-    public StrictModeFlash(DisplayContent dc) {
+    StrictModeFlash(Supplier<Surface> surfaceFactory, DisplayContent dc) {
+        mSurface = surfaceFactory.get();
         SurfaceControl ctrl = null;
         try {
             ctrl = dc.makeOverlay()
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 85176be..bbd986f 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -39,6 +39,8 @@
 import com.android.server.AnimationThread;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 
+import java.util.function.Supplier;
+
 /**
  * Class to run animations without holding the window manager lock.
  */
@@ -73,9 +75,10 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
-    SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
-        this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
-                powerManagerInternal);
+    SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
+            PowerManagerInternal powerManagerInternal) {
+        this(null /* callbackProvider */, null /* animatorFactory */,
+                transactionFactory.get(), powerManagerInternal);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index cd211a2..ba728ba 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -54,6 +54,7 @@
     final Animatable mAnimatable;
     private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
     @VisibleForTesting
+    @Nullable
     final Runnable mAnimationFinishedCallback;
     private boolean mAnimationStartDelayed;
 
@@ -262,7 +263,7 @@
             if (!mAnimationStartDelayed && forwardCancel) {
                 animation.onAnimationCancelled(leash);
             }
-            if (!restarting) {
+            if (!restarting && mAnimationFinishedCallback != null) {
                 mAnimationFinishedCallback.run();
             }
         }
diff --git a/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java b/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
deleted file mode 100644
index 5390e5a..0000000
--- a/services/core/java/com/android/server/wm/SurfaceBuilderFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import android.view.SurfaceSession;
-import android.view.SurfaceControl;
-
-interface SurfaceBuilderFactory {
-    SurfaceControl.Builder make(SurfaceSession s);
-};
-
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3a2eb57..85ba806 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -85,8 +85,6 @@
     private Rect mTmpRect = new Rect();
     // For handling display rotations.
     private Rect mTmpRect2 = new Rect();
-    // For retrieving dim bounds
-    private Rect mTmpRect3 = new Rect();
 
     // Resize mode of the task. See {@link ActivityInfo#resizeMode}
     private int mResizeMode;
@@ -613,6 +611,9 @@
 
     @Override
     public SurfaceControl getAnimationLeashParent() {
+        if (!WindowManagerService.sHierarchicalAnimations) {
+            return super.getAnimationLeashParent();
+        }
         // Currently, only the recents animation will create animation leashes for tasks. In this
         // case, reparent the task to the home animation layer while it is being animated to allow
         // the home activity to reorder the app windows relative to its own.
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index c2c4767..5e8831d 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -57,6 +57,7 @@
     private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
     private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 23;
     private static final int NOTIFY_TASK_LIST_UPDATED_LISTENERS_MSG = 24;
+    private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 25;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -161,6 +162,10 @@
         l.onSingleTaskDisplayDrawn(m.arg1);
     };
 
+    private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
+        l.onSingleTaskDisplayEmpty(m.arg1);
+    };
+
     private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
         l.onTaskDisplayChanged(m.arg1, m.arg2);
     };
@@ -251,6 +256,9 @@
                 case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
                     forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
                     break;
+                case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
+                    forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
+                    break;
                 case NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG:
                     forAllRemoteListeners(mNotifyTaskDisplayChanged, msg);
                     break;
@@ -513,6 +521,17 @@
     }
 
     /**
+     * Notify listeners that the last task is removed from a single task display.
+     */
+    void notifySingleTaskDisplayEmpty(int displayId) {
+        final Message msg = mHandler.obtainMessage(
+                NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
+                displayId, 0 /* unused */);
+        forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
+        msg.sendToTarget();
+    }
+
+    /**
      * Notify listeners that a task is reparented to another display.
      */
     void notifyTaskDisplayChanged(int taskId, int newDisplayId) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 8b0b6ce..42866f9 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -64,10 +64,6 @@
 
     private static Factory sFactory;
 
-    // The margin the pointer position has to be within the side of the screen to be
-    // considered at the side of the screen.
-    static final int SIDE_MARGIN_DIP = 100;
-
     @IntDef(flag = true,
             value = {
                     CTRL_NONE,
@@ -101,7 +97,6 @@
     private DisplayContent mDisplayContent;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private Rect mTmpRect = new Rect();
-    private int mSideMargin;
     private int mMinVisibleWidth;
     private int mMinVisibleHeight;
 
@@ -309,7 +304,6 @@
         // Notify InputMonitor to take mDragWindowHandle.
         mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
 
-        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
         display.getRealSize(mMaxVisibleSize);
@@ -488,12 +482,6 @@
         int right = mWindowOriginalBounds.right;
         int bottom = mWindowOriginalBounds.bottom;
 
-        // The aspect which we have to respect. Note that if the orientation does not need to be
-        // preserved the aspect will be calculated as 1.0 which neutralizes the following
-        // computations.
-        final float minAspect = !mPreserveOrientation
-                ? 1.0f
-                : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
         // Calculate the resulting width and height of the drag operation.
         int width = right - left;
         int height = bottom - top;
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index ede2f56..0ea108e 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -71,7 +71,6 @@
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK;
@@ -513,7 +512,7 @@
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
             // default configuration the next time it launches.
-            updateOverrideConfiguration(null);
+            setBounds(null);
         }
         mService.getTaskChangeNotificationController().notifyTaskRemoved(taskId);
     }
@@ -546,7 +545,7 @@
     }
 
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
-        mService.mWindowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
 
         try {
             if (!isResizeable()) {
@@ -566,7 +565,7 @@
                 // Task doesn't exist in window manager yet (e.g. was restored from recents).
                 // All we can do for now is update the bounds so it can be used when the task is
                 // added to window manager.
-                updateOverrideConfiguration(bounds);
+                setBounds(bounds);
                 if (!inFreeformWindowingMode()) {
                     // re-restore the task so it can have the proper stack association.
                     mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
@@ -585,7 +584,11 @@
 
             Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
 
-            final boolean updatedConfig = updateOverrideConfiguration(bounds);
+            boolean updatedConfig = false;
+            mTmpConfig.setTo(getResolvedOverrideConfiguration());
+            if (setBounds(bounds) != BOUNDS_CHANGE_NONE) {
+                updatedConfig = !mTmpConfig.equals(getResolvedOverrideConfiguration());
+            }
             // This variable holds information whether the configuration didn't change in a significant
 
             // way and the activity was kept the way it was. If it's false, it means the activity
@@ -616,7 +619,7 @@
             Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
             return kept;
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
     }
 
@@ -702,7 +705,7 @@
                 && toStack.topRunningActivityLocked() != null) {
             // Pause the resumed activity on the target stack while re-parenting task on top of it.
             toStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
-                    null /* resuming */, false /* pauseImmediately */);
+                    null /* resuming */);
         }
 
         final int toStackWindowingMode = toStack.getWindowingMode();
@@ -722,7 +725,7 @@
             windowManager.setWillReplaceWindow(topActivity.appToken, animate);
         }
 
-        windowManager.deferSurfaceLayout();
+        mService.deferWindowLayout();
         boolean kept = true;
         try {
             final ActivityRecord r = topRunningActivityLocked();
@@ -806,7 +809,7 @@
                         !mightReplaceWindow, deferResume);
             }
         } finally {
-            windowManager.continueSurfaceLayout();
+            mService.continueWindowLayout();
         }
 
         if (mightReplaceWindow) {
@@ -1082,8 +1085,8 @@
 
         clearRootProcess();
 
-        // TODO: Use window container controller once tasks are better synced between AM and WM
-        mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
+        mService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+                taskId, userId);
     }
 
     void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
@@ -1405,8 +1408,7 @@
      * Completely remove all activities associated with an existing
      * task starting at a specified index.
      */
-    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
-            String reason) {
+    final void performClearTaskAtIndexLocked(int activityNdx, String reason) {
         int numActivities = mActivities.size();
         for ( ; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
@@ -1419,8 +1421,8 @@
                 mActivities.remove(activityNdx);
                 --activityNdx;
                 --numActivities;
-            } else if (r.finishIfPossible(Activity.RESULT_CANCELED,
-                    null /* resultData */, reason, false /* oomAdj */, pauseImmediately)
+            } else if (r.finishIfPossible(Activity.RESULT_CANCELED, null /* resultData */, reason,
+                    false /* oomAdj */)
                     == FINISH_RESULT_REMOVED) {
                 --activityNdx;
                 --numActivities;
@@ -1433,7 +1435,7 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
+        performClearTaskAtIndexLocked(0, "clear-task-all");
         mReuseTask = false;
     }
 
@@ -1501,9 +1503,9 @@
         return null;
     }
 
-    void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
+    void removeTaskActivitiesLocked(String reason) {
         // Just remove the entire task.
-        performClearTaskAtIndexLocked(0, pauseImmediately, reason);
+        performClearTaskAtIndexLocked(0, reason);
     }
 
     String lockTaskAuthToString() {
@@ -1805,15 +1807,6 @@
         }
     }
 
-    /**
-     * Update task's override configuration based on the bounds.
-     * @param bounds The bounds of the task.
-     * @return True if the override configuration was updated.
-     */
-    boolean updateOverrideConfiguration(Rect bounds) {
-        return updateOverrideConfiguration(bounds, null /* insetBounds */);
-    }
-
     void setLastNonFullscreenBounds(Rect bounds) {
         if (mLastNonFullscreenBounds == null) {
             mLastNonFullscreenBounds = new Rect(bounds);
@@ -1823,32 +1816,6 @@
     }
 
     /**
-     * Update task's override configuration based on the bounds.
-     * @param bounds The bounds of the task.
-     * @param insetBounds The bounds used to calculate the system insets, which is used here to
-     *                    subtract the navigation bar/status bar size from the screen size reported
-     *                    to the application. See {@link IActivityTaskManager#resizeDockedStack}.
-     * @return True if the override configuration was updated.
-     */
-    boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
-        final boolean hasSetDisplayedBounds = (insetBounds != null && !insetBounds.isEmpty());
-        if (hasSetDisplayedBounds) {
-            setDisplayedBounds(bounds);
-        } else {
-            setDisplayedBounds(null);
-        }
-        // "steady" bounds do not include any temporary offsets from animation or interaction.
-        Rect steadyBounds = hasSetDisplayedBounds ? insetBounds : bounds;
-        if (equivalentRequestedOverrideBounds(steadyBounds)) {
-            return false;
-        }
-
-        mTmpConfig.setTo(getResolvedOverrideConfiguration());
-        setBounds(steadyBounds);
-        return !mTmpConfig.equals(getResolvedOverrideConfiguration());
-    }
-
-    /**
      * This should be called when an child activity changes state. This should only
      * be called from
      * {@link ActivityRecord#setState(ActivityState, String)} .
@@ -2299,7 +2266,7 @@
 
     Rect updateOverrideConfigurationFromLaunchBounds() {
         final Rect bounds = getLaunchBounds();
-        updateOverrideConfiguration(bounds);
+        setBounds(bounds);
         if (bounds != null && !bounds.isEmpty()) {
             // TODO: Review if we actually want to do this - we are setting the launch bounds
             // directly here.
@@ -2324,12 +2291,12 @@
                 return;
             }
             if (mLastNonFullscreenBounds != null) {
-                updateOverrideConfiguration(mLastNonFullscreenBounds);
+                setBounds(mLastNonFullscreenBounds);
             } else {
                 mService.mStackSupervisor.getLaunchParamsController().layoutTask(this, null);
             }
         } else {
-            updateOverrideConfiguration(inStack.getRequestedOverrideBounds());
+            setBounds(inStack.getRequestedOverrideBounds());
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index d6c2f66..d36ebf0 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -23,6 +23,8 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 
+import java.util.function.Function;
+
 /**
  * Class used by {@link RecentsAnimationController} to create a surface control with taking
  * screenshot of task when canceling recents animation.
@@ -36,7 +38,7 @@
     private int mWidth;
     private int mHeight;
 
-    TaskScreenshotAnimatable(Task task,
+    TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory, Task task,
             SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
         GraphicBuffer buffer = screenshotBuffer == null
                 ? null : screenshotBuffer.getGraphicBuffer();
@@ -47,7 +49,7 @@
             Slog.d(TAG, "Creating TaskScreenshotAnimatable: task: " + task
                     + "width: " + mWidth + "height: " + mHeight);
         }
-        mSurfaceControl = new SurfaceControl.Builder(new SurfaceSession())
+        mSurfaceControl = surfaceControlFactory.apply(new SurfaceSession())
                 .setName("RecentTaskScreenshotSurface")
                 .setBufferSize(mWidth, mHeight)
                 .build();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 32a1d90..a156f5c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -29,11 +29,11 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.AtomicFile;
 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 1b3e4c1..7456f0d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -237,7 +237,7 @@
             int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
             int currentOrientation) {
         mService = service;
-        mSurface = new Surface();
+        mSurface = service.mSurfaceFactory.get();
         mHandler = new Handler(mService.mH.getLooper());
         mSession = WindowManagerGlobal.getWindowSession();
         mWindow = window;
@@ -325,13 +325,13 @@
                 - ((float) mFrame.width() / mFrame.height())) > 0.01f;
 
         // Keep a reference to it such that it doesn't get destroyed when finalized.
-        mChildSurfaceControl = new SurfaceControl.Builder(session)
+        mChildSurfaceControl = mService.mSurfaceControlFactory.apply(session)
                 .setName(mTitle + " - task-snapshot-surface")
                 .setBufferSize(buffer.getWidth(), buffer.getHeight())
                 .setFormat(buffer.getFormat())
                 .setParent(mSurfaceControl)
                 .build();
-        Surface surface = new Surface();
+        Surface surface = mService.mSurfaceFactory.get();
         surface.copyFrom(mChildSurfaceControl);
 
         final Rect frame;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index cc2112e..10d8328 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -45,7 +45,6 @@
 import static com.android.server.wm.StackProto.ADJUST_DIVIDER_AMOUNT;
 import static com.android.server.wm.StackProto.ADJUST_IME_AMOUNT;
 import static com.android.server.wm.StackProto.ANIMATING_BOUNDS;
-import static com.android.server.wm.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
 import static com.android.server.wm.StackProto.BOUNDS;
 import static com.android.server.wm.StackProto.DEFER_REMOVAL;
 import static com.android.server.wm.StackProto.FILLS_PARENT;
@@ -111,12 +110,6 @@
      */
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
-    private SurfaceControl mAnimationBackgroundSurface;
-    private boolean mAnimationBackgroundSurfaceIsShown = false;
-
-    /** The particular window with an Animation with non-zero background color. */
-    private WindowStateAnimator mAnimationBackgroundAnimator;
-
     /** Application tokens that are exiting, but still on screen for animations. */
     final AppTokenList mExitingAppTokens = new AppTokenList();
     final AppTokenList mTmpAppTokens = new AppTokenList();
@@ -230,39 +223,6 @@
         }
     }
 
-    private void updateAnimationBackgroundBounds() {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getRawBounds(mTmpRect);
-        final Rect stackBounds = getBounds();
-        getPendingTransaction()
-                .setWindowCrop(mAnimationBackgroundSurface, mTmpRect.width(), mTmpRect.height())
-                .setPosition(mAnimationBackgroundSurface, mTmpRect.left - stackBounds.left,
-                        mTmpRect.top - stackBounds.top);
-        scheduleAnimation();
-    }
-
-    private void hideAnimationSurface() {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getPendingTransaction().hide(mAnimationBackgroundSurface);
-        mAnimationBackgroundSurfaceIsShown = false;
-        scheduleAnimation();
-    }
-
-    private void showAnimationSurface(float alpha) {
-        if (mAnimationBackgroundSurface == null) {
-            return;
-        }
-        getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
-                .setAlpha(mAnimationBackgroundSurface, alpha)
-                .show(mAnimationBackgroundSurface);
-        mAnimationBackgroundSurfaceIsShown = true;
-        scheduleAnimation();
-    }
-
     @Override
     public int setBounds(Rect bounds) {
         return setBounds(getRequestedOverrideBounds(), bounds);
@@ -275,10 +235,6 @@
 
         final int result = super.setBounds(bounds);
 
-        if (getParent() != null) {
-            updateAnimationBackgroundBounds();
-        }
-
         updateAdjustedBounds();
 
         updateSurfaceBounds();
@@ -738,7 +694,6 @@
         // surface position.
         updateSurfaceSize(getPendingTransaction());
         final int windowingMode = getWindowingMode();
-        final boolean isAlwaysOnTop = isAlwaysOnTop();
 
         if (mDisplayContent == null) {
             return;
@@ -822,11 +777,6 @@
         super.onDisplayChanged(dc);
 
         updateSurfaceBounds();
-        if (mAnimationBackgroundSurface == null) {
-            mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer()
-                    .setName("animation background stackId=" + mStackId)
-                    .build();
-        }
     }
 
     /**
@@ -859,7 +809,7 @@
         // When the home stack is resizable, should always have the same stack and task bounds
         if (isActivityTypeHome()) {
             final Task homeTask = findHomeTask();
-            if (homeTask != null && homeTask.isResizeable()) {
+            if (homeTask == null || homeTask.isResizeable()) {
                 // Calculate the home stack bounds when in docked mode and the home stack is
                 // resizeable.
                 getDisplayContent().mDividerControllerLocked
@@ -965,7 +915,7 @@
     }
 
     void resetDockedStackToMiddle() {
-        if (inSplitScreenPrimaryWindowingMode()) {
+        if (!inSplitScreenPrimaryWindowingMode()) {
             throw new IllegalStateException("Not a docked stack=" + this);
         }
 
@@ -973,12 +923,12 @@
 
         final Rect bounds = new Rect();
         final Rect tempBounds = new Rect();
-        TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
-        Rect dockedBounds =
-                (dockedStack == null || dockedStack == this) ? null : dockedStack.getRawBounds();
-        getStackDockedModeBoundsLocked(mDisplayContent.getConfiguration(), dockedBounds,
+        getStackDockedModeBoundsLocked(mDisplayContent.getConfiguration(), null /* dockedBounds */,
                 null /* currentTempTaskBounds */, bounds, tempBounds);
-        mActivityStack.requestResize(bounds);
+        mActivityStack.mStackSupervisor.resizeDockedStackLocked(bounds, null /* tempTaskBounds */,
+                null /* tempTaskInsetBounds */, null /* tempOtherTaskBounds */,
+                null /* tempOtherTaskInsetBounds */, false /* preserveWindows */,
+                false /* deferResume */);
     }
 
     @Override
@@ -1008,27 +958,10 @@
 
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
-        if (mAnimationBackgroundSurface != null) {
-            mWmService.mTransactionFactory.make().remove(mAnimationBackgroundSurface).apply();
-            mAnimationBackgroundSurface = null;
-        }
-
         mDisplayContent = null;
         mWmService.mWindowPlacerLocked.requestTraversal();
     }
 
-    void resetAnimationBackgroundAnimator() {
-        mAnimationBackgroundAnimator = null;
-        hideAnimationSurface();
-    }
-
-    void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
-        if (mAnimationBackgroundAnimator == null) {
-            mAnimationBackgroundAnimator = winAnimator;
-            showAnimationSurface(((color >> 24) & 0xff) / 255f);
-        }
-    }
-
     // TODO: Should each user have there own stacks?
     @Override
     void switchUser() {
@@ -1365,7 +1298,6 @@
         }
         proto.write(FILLS_PARENT, matchParentBounds());
         getRawBounds().writeToProto(proto, BOUNDS);
-        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
         proto.write(DEFER_REMOVAL, mDeferRemoval);
         proto.write(MINIMIZE_AMOUNT, mMinimizeAmount);
         proto.write(ADJUSTED_FOR_IME, mAdjustedForIme);
@@ -1395,9 +1327,6 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).dump(pw, prefix + "  ", dumpAll);
         }
-        if (mAnimationBackgroundSurfaceIsShown) {
-            pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
-        }
         if (!mExitingAppTokens.isEmpty()) {
             pw.println();
             pw.println("  Exiting application tokens:");
@@ -1661,40 +1590,6 @@
     }
 
     /**
-     * @return the current stack bounds transformed to the given {@param aspectRatio}. If
-     *         the default bounds is {@code null}, then the {@param aspectRatio} is applied to the
-     *         default bounds.
-     */
-    Rect getPictureInPictureBounds(float aspectRatio, Rect stackBounds) {
-        if (!mWmService.mAtmService.mSupportsPictureInPicture) {
-            return null;
-        }
-
-        final DisplayContent displayContent = getDisplayContent();
-        if (displayContent == null) {
-            return null;
-        }
-
-        if (!inPinnedWindowingMode()) {
-            return null;
-        }
-
-        final PinnedStackController pinnedStackController =
-                displayContent.getPinnedStackController();
-        if (stackBounds == null) {
-            // Calculate the aspect ratio bounds from the default bounds
-            stackBounds = pinnedStackController.getDefaultOrLastSavedBounds();
-        }
-
-        if (pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)) {
-            return pinnedStackController.transformBoundsToAspectRatio(stackBounds, aspectRatio,
-                    true /* useCurrentMinEdgeSize */);
-        } else {
-            return stackBounds;
-        }
-    }
-
-    /**
      * Animates the pinned stack.
      */
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
@@ -1746,6 +1641,11 @@
             if (toBounds.width() == fromBounds.width()
                     && toBounds.height() == fromBounds.height()) {
                 intendedAnimationType = BoundsAnimationController.BOUNDS;
+            } else if (!fromFullscreen && !toBounds.equals(fromBounds)) {
+                // intendedAnimationType may have been reset at the end of RecentsAnimation,
+                // force it to BOUNDS type if we know for certain we're animating to
+                // a different bounds, especially for expand and collapse of PiP window.
+                intendedAnimationType = BoundsAnimationController.BOUNDS;
             }
         }
 
@@ -1766,6 +1666,11 @@
             return;
         }
 
+        final DisplayContent displayContent = getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
         if (!inPinnedWindowingMode()) {
             return;
         }
@@ -1776,13 +1681,10 @@
         if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) == 0) {
             return;
         }
-        getAnimationOrCurrentBounds(mTmpFromBounds);
-        mTmpToBounds.set(mTmpFromBounds);
-        getPictureInPictureBounds(aspectRatio, mTmpToBounds);
-        if (!mTmpToBounds.equals(mTmpFromBounds)) {
-            animateResizePinnedStack(mTmpToBounds, null /* sourceHintBounds */,
-                    -1 /* duration */, false /* fromFullscreen */);
-        }
+
+        // Notify the pinned stack controller about aspect ratio change.
+        // This would result a callback delivered from SystemUI to WM to start animation,
+        // if the bounds are ought to be altered due to aspect ratio change.
         pinnedStackController.setAspectRatio(
                 pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
                         ? aspectRatio : -1f);
diff --git a/services/core/java/com/android/server/wm/TransactionFactory.java b/services/core/java/com/android/server/wm/TransactionFactory.java
deleted file mode 100644
index 067f083..0000000
--- a/services/core/java/com/android/server/wm/TransactionFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import android.view.SurfaceControl.Transaction;
-
-/**
- * Helper class to inject custom transaction objects into window manager.
- */
-interface TransactionFactory {
-    Transaction make();
-};
-
diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
new file mode 100644
index 0000000..895350b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationAdapterProto.REMOTE;
+import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_REMOTE_ANIMATIONS;
+
+import android.graphics.Point;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * An animation adapter for wallpaper windows.
+ */
+class WallpaperAnimationAdapter implements AnimationAdapter {
+    private static final String TAG = "WallpaperAnimationAdapter";
+
+    private final WallpaperWindowToken mWallpaperToken;
+    private SurfaceControl mCapturedLeash;
+    private SurfaceAnimator.OnAnimationFinishedCallback mCapturedLeashFinishCallback;
+
+    private long mDurationHint;
+    private long mStatusBarTransitionDelay;
+
+    private Consumer<WallpaperAnimationAdapter> mAnimationCanceledRunnable;
+    private RemoteAnimationTarget mTarget;
+
+    WallpaperAnimationAdapter(WallpaperWindowToken wallpaperToken,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable) {
+        mWallpaperToken = wallpaperToken;
+        mDurationHint = durationHint;
+        mStatusBarTransitionDelay = statusBarTransitionDelay;
+        mAnimationCanceledRunnable = animationCanceledRunnable;
+    }
+
+    /**
+     * Creates and starts remote animations for all the visible wallpaper windows.
+     *
+     * @return RemoteAnimationTarget[] targets for all the visible wallpaper windows
+     */
+    public static RemoteAnimationTarget[] startWallpaperAnimations(WindowManagerService service,
+            long durationHint, long statusBarTransitionDelay,
+            Consumer<WallpaperAnimationAdapter> animationCanceledRunnable,
+            ArrayList<WallpaperAnimationAdapter> adaptersOut) {
+        final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
+        service.mRoot.forAllWallpaperWindows(wallpaperWindow -> {
+            if (!wallpaperWindow.getDisplayContent().mWallpaperController.isWallpaperVisible()) {
+                if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                    Slog.d(TAG, "\tNot visible=" + wallpaperWindow);
+                }
+                return;
+            }
+
+            if (DEBUG_REMOTE_ANIMATIONS || DEBUG_RECENTS_ANIMATIONS) {
+                Slog.d(TAG, "\tvisible=" + wallpaperWindow);
+            }
+            final WallpaperAnimationAdapter wallpaperAdapter = new WallpaperAnimationAdapter(
+                    wallpaperWindow, durationHint, statusBarTransitionDelay,
+                    animationCanceledRunnable);
+            wallpaperWindow.startAnimation(wallpaperWindow.getPendingTransaction(),
+                    wallpaperAdapter, false /* hidden */);
+            targets.add(wallpaperAdapter.createRemoteAnimationTarget());
+            adaptersOut.add(wallpaperAdapter);
+        });
+        return targets.toArray(new RemoteAnimationTarget[targets.size()]);
+    }
+
+    /**
+     * Create a remote animation target for this animation adapter.
+     */
+    RemoteAnimationTarget createRemoteAnimationTarget() {
+        mTarget = new RemoteAnimationTarget(-1, -1, getLeash(), false, null, null,
+                mWallpaperToken.getPrefixOrderIndex(), new Point(), null,
+                mWallpaperToken.getWindowConfiguration(), true, null, null);
+        return mTarget;
+    }
+
+    /**
+     * @return the leash for this animation (only valid after the wallpaper window surface animation
+     * has started).
+     */
+    SurfaceControl getLeash() {
+        return mCapturedLeash;
+    }
+
+    /**
+     * @return the callback to call to clean up when the animation has finished.
+     */
+    SurfaceAnimator.OnAnimationFinishedCallback getLeashFinishedCallback() {
+        return mCapturedLeashFinishCallback;
+    }
+
+    /**
+     * @return the wallpaper window
+     */
+    WallpaperWindowToken getToken() {
+        return mWallpaperToken;
+    }
+
+    @Override
+    public boolean getShowWallpaper() {
+        // Not used
+        return false;
+    }
+
+    @Override
+    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
+            SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "startAnimation");
+
+        // Restore z-layering until client has a chance to modify it.
+        t.setLayer(animationLeash, mWallpaperToken.getPrefixOrderIndex());
+        mCapturedLeash = animationLeash;
+        mCapturedLeashFinishCallback = finishCallback;
+    }
+
+    @Override
+    public void onAnimationCancelled(SurfaceControl animationLeash) {
+        if (DEBUG_REMOTE_ANIMATIONS) Slog.d(TAG, "onAnimationCancelled");
+        mAnimationCanceledRunnable.accept(this);
+    }
+
+    @Override
+    public long getDurationHint() {
+        return mDurationHint;
+    }
+
+    @Override
+    public long getStatusBarTransitionsStartTime() {
+        return SystemClock.uptimeMillis() + mStatusBarTransitionDelay;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("token=");
+        pw.println(mWallpaperToken);
+        if (mTarget != null) {
+            pw.print(prefix);
+            pw.println("Target:");
+            mTarget.dump(pw, prefix + "  ");
+        } else {
+            pw.print(prefix);
+            pw.println("Target: null");
+        }
+    }
+
+    @Override
+    public void writeToProto(ProtoOutputStream proto) {
+        final long token = proto.start(REMOTE);
+        if (mTarget != null) {
+            mTarget.writeToProto(proto, TARGET);
+        }
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6147272..13902ee 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -110,7 +110,6 @@
     private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
 
     private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
-        final WindowAnimator winAnimator = mService.mAnimator;
         if ((w.mAttrs.type == TYPE_WALLPAPER)) {
             if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
                 mFindResults.setTopWallpaper(w);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e15b783..528cece 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -29,6 +30,8 @@
 import android.view.DisplayInfo;
 import android.view.animation.Animation;
 
+import java.util.function.Consumer;
+
 /**
  * A token that represents a set of wallpaper windows.
  */
@@ -153,6 +156,11 @@
     }
 
     @Override
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        callback.accept(this);
+    }
+
+    @Override
     public String toString() {
         if (stringName == null) {
             StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index e6ac059..729cfc0 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -33,6 +33,8 @@
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 
+import java.util.function.Supplier;
+
 /**
  * Displays a watermark on top of the window manager's windows.
  */
@@ -47,19 +49,20 @@
     private final int mDeltaY;
 
     private final SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private final Surface mSurface;
     private int mLastDW;
     private int mLastDH;
     private boolean mDrawNeeded;
 
-    Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
+    Watermark(Supplier<Surface> surfaceFactory, DisplayContent dc, DisplayMetrics dm,
+            String[] tokens) {
         if (false) {
             Log.i(TAG_WM, "*********************** WATERMARK");
             for (int i=0; i<tokens.length; i++) {
                 Log.i(TAG_WM, "  TOKEN #" + i + ": " + tokens[i]);
             }
         }
-
+        mSurface = surfaceFactory.get();
         mDisplay = dc.getDisplay();
         mTokens = tokens;
 
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index d8ebd84..7c183a8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -19,7 +19,6 @@
 import static com.android.server.wm.AnimationAdapter.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.server.wm.AnimationSpecProto.WINDOW;
 import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 
 import android.graphics.Point;
@@ -81,11 +80,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return mAnimation.getBackgroundColor();
-    }
-
-    @Override
     public long getDuration() {
         return mAnimation.computeDurationHint();
     }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index b8db98b..c7916e8 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -83,12 +83,13 @@
     private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>();
     private boolean mInExecuteAfterPrepareSurfacesRunnables;
 
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTransaction;
 
     WindowAnimator(final WindowManagerService service) {
         mService = service;
         mContext = service.mContext;
         mPolicy = service.mPolicy;
+        mTransaction = service.mTransactionFactory.get();
         AnimationThread.getHandler().runWithScissors(
                 () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);
 
@@ -179,7 +180,6 @@
                     // Update animations of all applications, including those
                     // associated with exiting/removed apps
                     dc.updateWindowsForAnimator();
-                    dc.updateBackgroundForAnimator();
                     dc.prepareSurfaces();
                 }
 
@@ -305,24 +305,6 @@
         }
     }
 
-    int getPendingLayoutChanges(final int displayId) {
-        if (displayId < 0) {
-            return 0;
-        }
-        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
-        return (displayContent != null) ? displayContent.pendingLayoutChanges : 0;
-    }
-
-    void setPendingLayoutChanges(final int displayId, final int changes) {
-        if (displayId < 0) {
-            return;
-        }
-        final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
-        if (displayContent != null) {
-            displayContent.pendingLayoutChanges |= changes;
-        }
-    }
-
     private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
         if (displayId < 0) {
             return null;
diff --git a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
index 775d5b2..d5d4e08 100644
--- a/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowChangeAnimationSpec.java
@@ -70,11 +70,6 @@
     }
 
     @Override
-    public int getBackgroundColor() {
-        return 0;
-    }
-
-    @Override
     public long getDuration() {
         return mAnimation.getDuration();
     }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 29d232f..586375f 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -139,7 +139,7 @@
 
     WindowContainer(WindowManagerService wms) {
         mWmService = wms;
-        mPendingTransaction = wms.mTransactionFactory.make();
+        mPendingTransaction = wms.mTransactionFactory.get();
         mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
     }
 
@@ -895,6 +895,12 @@
         }
     }
 
+    void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).forAllWallpaperWindows(callback);
+        }
+    }
+
     /**
      * For all tasks at or below this container call the callback.
      *
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d49d245..14214b4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -28,6 +28,7 @@
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.Process.SYSTEM_UID;
@@ -36,7 +37,9 @@
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS;
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.DOCKED_INVALID;
@@ -283,6 +286,8 @@
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
@@ -292,7 +297,6 @@
     static final int LAYOUT_REPEAT_THRESHOLD = 4;
 
     static final boolean PROFILE_ORIENTATION = false;
-    static final boolean localLOGV = DEBUG;
 
     /** How much to multiply the policy's type layer, to reserve room
      * for multiple windows of the same type and Z-ordering adjustment
@@ -309,11 +313,6 @@
     static final int WINDOW_LAYER_MULTIPLIER = 5;
 
     /**
-     * Dim surface layer is immediately below target window.
-     */
-    static final int LAYER_OFFSET_DIM = 1;
-
-    /**
      * Animation thumbnail is as far as possible below the window above
      * the thumbnail (or in other words as far as possible above the window
      * below it).
@@ -363,14 +362,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})
@@ -666,7 +679,8 @@
     WindowManagerInternal.OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
     SettingsObserver mSettingsObserver;
 
-    private final class SettingsObserver extends ContentObserver {
+    @VisibleForTesting
+    final class SettingsObserver extends ContentObserver {
         private final Uri mDisplayInversionEnabledUri =
                 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
         private final Uri mWindowAnimationScaleUri =
@@ -681,6 +695,12 @@
                 Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
         private final Uri mPointerLocationUri =
                 Settings.System.getUriFor(Settings.System.POINTER_LOCATION);
+        private final Uri mForceDesktopModeOnExternalDisplaysUri = Settings.Global.getUriFor(
+                        Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS);
+        private final Uri mFreeformWindowUri = Settings.Global.getUriFor(
+                Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT);
+        private final Uri mForceResizableUri = Settings.Global.getUriFor(
+                DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
 
         public SettingsObserver() {
             super(new Handler());
@@ -697,6 +717,10 @@
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mPolicyControlUri, false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(mPointerLocationUri, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mForceDesktopModeOnExternalDisplaysUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mFreeformWindowUri, false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(mForceResizableUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -720,6 +744,21 @@
                 return;
             }
 
+            if (mForceDesktopModeOnExternalDisplaysUri.equals(uri)) {
+                updateForceDesktopModeOnExternalDisplays();
+                return;
+            }
+
+            if (mFreeformWindowUri.equals(uri)) {
+                updateFreeformWindowManagement();
+                return;
+            }
+
+            if (mForceResizableUri.equals(uri)) {
+                updateForceResizableTasks();
+                return;
+            }
+
             @UpdateAnimationScaleMode
             final int mode;
             if (mWindowAnimationScaleUri.equals(uri)) {
@@ -762,6 +801,33 @@
                         mPointerLocationEnabled));
             }
         }
+
+        void updateForceDesktopModeOnExternalDisplays() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean enableForceDesktopMode = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+            if (mForceDesktopModeOnExternalDisplays == enableForceDesktopMode) {
+                return;
+            }
+            setForceDesktopModeOnExternalDisplays(enableForceDesktopMode);
+        }
+
+        void updateFreeformWindowManagement() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean freeformWindowManagement = mContext.getPackageManager().hasSystemFeature(
+                    FEATURE_FREEFORM_WINDOW_MANAGEMENT) || Settings.Global.getInt(
+                    resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
+
+            mAtmService.mSupportsFreeformWindowManagement = freeformWindowManagement;
+        }
+
+        void updateForceResizableTasks() {
+            ContentResolver resolver = mContext.getContentResolver();
+            final boolean forceResizable = Settings.Global.getInt(resolver,
+                    DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
+
+            mAtmService.mForceResizableActivities = forceResizable;
+        }
     }
 
     PowerManager mPowerManager;
@@ -855,9 +921,9 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
-    SurfaceBuilderFactory mSurfaceBuilderFactory = SurfaceControl.Builder::new;
-    TransactionFactory mTransactionFactory = SurfaceControl.Transaction::new;
-    SurfaceFactory mSurfaceFactory = Surface::new;
+    Function<SurfaceSession, SurfaceControl.Builder> mSurfaceControlFactory;
+    Supplier<SurfaceControl.Transaction> mTransactionFactory;
+    final Supplier<Surface> mSurfaceFactory;
 
     private final SurfaceControl.Transaction mTransaction;
 
@@ -957,20 +1023,22 @@
             final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
             ActivityTaskManagerService atm) {
         return main(context, im, showBootMsgs, onlyCore, policy, atm,
-                SurfaceControl.Transaction::new);
+                SurfaceControl.Transaction::new, Surface::new, SurfaceControl.Builder::new);
     }
 
     /**
      * Creates and returns an instance of the WindowManagerService. This call allows the caller
-     * to override the {@link TransactionFactory} to stub functionality under test.
+     * to override factories that can be used to stub native calls during test.
      */
     @VisibleForTesting
     public static WindowManagerService main(final Context context, final InputManagerService im,
             final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
-            ActivityTaskManagerService atm, TransactionFactory transactionFactory) {
+            ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
+            Supplier<Surface> surfaceFactory,
+            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
         DisplayThread.getHandler().runWithScissors(() ->
                 sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
-                        atm, transactionFactory), 0);
+                        atm, transactionFactory, surfaceFactory, surfaceControlFactory), 0);
         return sInstance;
     }
 
@@ -992,7 +1060,9 @@
 
     private WindowManagerService(Context context, InputManagerService inputManager,
             boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,
-            ActivityTaskManagerService atm, TransactionFactory transactionFactory) {
+            ActivityTaskManagerService atm, Supplier<SurfaceControl.Transaction> transactionFactory,
+            Supplier<Surface> surfaceFactory,
+            Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
         installLock(this, INDEX_WINDOW);
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
@@ -1020,10 +1090,13 @@
                 com.android.internal.R.bool.config_lowRamTaskSnapshotsAndRecents);
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
-        mDisplayWindowSettings = new DisplayWindowSettings(this);
 
+        mSurfaceControlFactory = surfaceControlFactory;
         mTransactionFactory = transactionFactory;
-        mTransaction = mTransactionFactory.make();
+        mSurfaceFactory = surfaceFactory;
+        mTransaction = mTransactionFactory.get();
+
+        mDisplayWindowSettings = new DisplayWindowSettings(this);
         mPolicy = policy;
         mAnimator = new WindowAnimator(this);
         mRoot = new RootWindowContainer(this);
@@ -1127,7 +1200,8 @@
                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
         mHoldingScreenWakeLock.setReferenceCounted(false);
 
-        mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);
+        mSurfaceAnimationRunner = new SurfaceAnimationRunner(mTransactionFactory,
+                mPowerManagerInternal);
 
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
@@ -1151,7 +1225,8 @@
         mPropertiesChangedListener = properties -> {
             synchronized (mGlobalLock) {
                 final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP,
-                        properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
+                        DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                                KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0));
                 final boolean excludedByPreQSticky = DeviceConfig.getBoolean(
                         DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                         KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE, false);
@@ -1605,8 +1680,10 @@
             }
             displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
 
-            if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
-                    + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
+            if (DEBUG || DEBUG_ADD_REMOVE) {
+                Slog.v(TAG_WM, "addWindow: New client " + client.asBinder()
+                        + ": window=" + win + " Callers=" + Debug.getCallers(5));
+            }
 
             if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
                 displayContent.sendNewConfiguration();
@@ -1980,13 +2057,11 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         long origId = Binder.clearCallingIdentity();
-        final int displayId;
         synchronized (mGlobalLock) {
             final WindowState win = windowForClientLocked(session, client, false);
             if (win == null) {
                 return 0;
             }
-            displayId = win.getDisplayId();
             final DisplayContent displayContent = win.getDisplayContent();
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
 
@@ -2273,16 +2348,18 @@
             outCutout.set(win.getWmDisplayCutout().getDisplayCutout());
             outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw()));
             outInsetsState.set(displayContent.getInsetsStateController().getInsetsForDispatch(win));
-            if (localLOGV) Slog.v(
-                TAG_WM, "Relayout given client " + client.asBinder()
-                + ", requestedWidth=" + requestedWidth
-                + ", requestedHeight=" + requestedHeight
-                + ", viewVisibility=" + viewVisibility
-                + "\nRelayout returning frame=" + outFrame
-                + ", surface=" + outSurfaceControl);
+            if (DEBUG) {
+                Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
+                                + ", requestedWidth=" + requestedWidth
+                                + ", requestedHeight=" + requestedHeight
+                                + ", viewVisibility=" + viewVisibility
+                                + "\nRelayout returning frame=" + outFrame
+                                + ", surface=" + outSurfaceControl);
+            }
 
-            if (localLOGV || DEBUG_FOCUS) Slog.v(
-                TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+            if (DEBUG || DEBUG_FOCUS) {
+                Slog.v(TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+            }
 
             result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
 
@@ -2562,16 +2639,14 @@
         getDefaultDisplayContentLocked().executeAppTransition();
     }
 
-    public void initializeRecentsAnimation(int targetActivityType,
+    void initializeRecentsAnimation(int targetActivityType,
             IRecentsAnimationRunner recentsAnimationRunner,
             RecentsAnimationController.RecentsAnimationCallbacks callbacks, int displayId,
             SparseBooleanArray recentTaskIds) {
-        synchronized (mGlobalLock) {
-            mRecentsAnimationController = new RecentsAnimationController(this,
-                    recentsAnimationRunner, callbacks, displayId);
-            mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
-            mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
-        }
+        mRecentsAnimationController = new RecentsAnimationController(this, recentsAnimationRunner,
+                callbacks, displayId);
+        mRoot.getDisplayContent(displayId).mAppTransition.updateBooster();
+        mRecentsAnimationController.initialize(targetActivityType, recentTaskIds);
     }
 
     @VisibleForTesting
@@ -2709,21 +2784,6 @@
     }
 
     /**
-     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
-     * performance, only one layout pass should be done. This can be called multiple times, and
-     * layouting will be resumed once the last caller has called
-     * {@link #continueSurfaceLayout}.
-     */
-    void deferSurfaceLayout() {
-        mWindowPlacerLocked.deferLayout();
-    }
-
-    /** Resumes layout passes after deferring them. See {@link #deferSurfaceLayout()} */
-    void continueSurfaceLayout() {
-        mWindowPlacerLocked.continueLayout();
-    }
-
-    /**
      * Notifies activity manager that some Keyguard flags have changed and that it needs to
      * reevaluate the visibilities of the activities.
      * @param callback Runnable to be called when activity manager is done reevaluating visibilities
@@ -2732,12 +2792,6 @@
         mAtmInternal.notifyKeyguardFlagsChanged(callback, displayId);
     }
 
-    public boolean isKeyguardTrusted() {
-        synchronized (mGlobalLock) {
-            return mPolicy.isKeyguardTrustedLw();
-        }
-    }
-
     public void setKeyguardGoingAway(boolean keyguardGoingAway) {
         synchronized (mGlobalLock) {
             mKeyguardGoingAway = keyguardGoingAway;
@@ -2883,13 +2937,6 @@
         }
     }
 
-    public boolean isShowingDream() {
-        synchronized (mGlobalLock) {
-            // TODO(b/123372519): Fix this when dream can be shown on non-default display.
-            return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
-        }
-    }
-
     @Override
     public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) {
         if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
@@ -2900,12 +2947,6 @@
         }
     }
 
-    public void onKeyguardOccludedChanged(boolean occluded) {
-        synchronized (mGlobalLock) {
-            mPolicy.onKeyguardOccludedChangedLw(occluded);
-        }
-    }
-
     @Override
     public void setSwitchingUser(boolean switching) {
         if (!checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -3404,7 +3445,7 @@
                         int maskThickness = mContext.getResources().getDimensionPixelSize(
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
-                        mCircularDisplayMask = new CircularDisplayMask(
+                        mCircularDisplayMask = new CircularDisplayMask(mSurfaceFactory,
                                 getDefaultDisplayContentLocked(),
                                 mPolicy.getWindowLayerFromTypeLw(
                                         WindowManager.LayoutParams.TYPE_POINTER)
@@ -3432,6 +3473,7 @@
             try {
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
+                            mSurfaceFactory,
                             mContext,
                             getDefaultDisplayContentLocked(),
                             mPolicy.getWindowLayerFromTypeLw(
@@ -3480,7 +3522,7 @@
             try {
                 // TODO(multi-display): support multiple displays
                 if (mStrictModeFlash == null) {
-                    mStrictModeFlash = new StrictModeFlash(
+                    mStrictModeFlash = new StrictModeFlash(mSurfaceFactory,
                             getDefaultDisplayContentLocked());
                 }
                 mStrictModeFlash.setVisibility(on);
@@ -5175,7 +5217,7 @@
 
     final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
         WindowState win = mWindowMap.get(client);
-        if (localLOGV) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
+        if (DEBUG) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
         if (win == null) {
             if (throwOnError) {
                 throw new IllegalArgumentException(
@@ -5275,9 +5317,7 @@
     }
 
     void requestTraversal() {
-        synchronized (mGlobalLock) {
-            mWindowPlacerLocked.requestTraversal();
-        }
+        mWindowPlacerLocked.requestTraversal();
     }
 
     /** Note that Locked in this case is on mLayoutToAnim */
@@ -5497,7 +5537,8 @@
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
                     final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+                    mWatermark = new Watermark(mSurfaceFactory, displayContent,
+                            displayContent.mRealDisplayMetrics,
                             toks);
                 }
             }
@@ -5744,55 +5785,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);
@@ -6302,16 +6294,6 @@
         }
     }
 
-    public void onDisplayChanged(int displayId) {
-        synchronized (mGlobalLock) {
-            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            if (displayContent != null) {
-                displayContent.updateDisplayInfo();
-            }
-            mWindowPlacerLocked.requestTraversal();
-        }
-    }
-
     @Override
     public Object getWindowManagerLock() {
         return mGlobalLock;
@@ -6322,21 +6304,18 @@
      * a window.
      * @param token Application token for which the activity will be relaunched.
      */
-    public void setWillReplaceWindow(IBinder token, boolean animate) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null) {
-                Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token "
-                        + token);
-                return;
-            }
-            if (!appWindowToken.hasContentToDisplay()) {
-                Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
-                        + token);
-                return;
-            }
-            appWindowToken.setWillReplaceWindows(animate);
+    void setWillReplaceWindow(IBinder token, boolean animate) {
+        final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+        if (appWindowToken == null) {
+            Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
+            return;
         }
+        if (!appWindowToken.hasContentToDisplay()) {
+            Slog.w(TAG_WM, "Attempted to set replacing window on app token with no content"
+                    + token);
+            return;
+        }
+        appWindowToken.setWillReplaceWindows(animate);
     }
 
     /**
@@ -6384,19 +6363,17 @@
      * @param token Application token for the activity whose window might be replaced.
      * @param replacing Whether the window is being replaced or not.
      */
-    public void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
-        synchronized (mGlobalLock) {
-            final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
-            if (appWindowToken == null) {
-                Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
-                        + token);
-                return;
-            }
-            if (replacing) {
-                scheduleWindowReplacementTimeouts(appWindowToken);
-            } else {
-                appWindowToken.clearWillReplaceWindows();
-            }
+    void scheduleClearWillReplaceWindows(IBinder token, boolean replacing) {
+        final AppWindowToken appWindowToken = mRoot.getAppWindowToken(token);
+        if (appWindowToken == null) {
+            Slog.w(TAG_WM, "Attempted to reset replacing window on non-existing app token "
+                    + token);
+            return;
+        }
+        if (replacing) {
+            scheduleWindowReplacementTimeouts(appWindowToken);
+        } else {
+            appWindowToken.clearWillReplaceWindows();
         }
     }
 
@@ -6418,11 +6395,9 @@
         }
     }
 
-    public void setDockedStackResizing(boolean resizing) {
-        synchronized (mGlobalLock) {
-            getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
-            requestTraversal();
-        }
+    void setDockedStackResizing(boolean resizing) {
+        getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
+        requestTraversal();
     }
 
     @Override
@@ -6967,7 +6942,9 @@
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
-            requestTraversal();
+            synchronized (mGlobalLock) {
+                requestTraversal();
+            }
         }
 
         @Override
@@ -7488,7 +7465,7 @@
     }
 
     SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
-        return mSurfaceBuilderFactory.make(s);
+        return mSurfaceControlFactory.apply(s);
     }
 
     /**
@@ -7560,7 +7537,8 @@
             mRoot.forAllDisplays(displayContent ->
                     displayContent.getInputMonitor().updateInputWindowsImmediately());
         }
-        new SurfaceControl.Transaction().syncInputWindows().apply(true);
+
+        mTransactionFactory.get().syncInputWindows().apply(true);
     }
 
     private void waitForAnimationsToComplete() {
@@ -7682,7 +7660,7 @@
         h.replaceTouchableRegionWithCrop(null);
 
         SurfaceSession s = new SurfaceSession();
-        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        SurfaceControl.Transaction t = mTransactionFactory.get();
         t.setInputWindowInfo(surface, h);
         t.apply();
 
@@ -7691,4 +7669,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/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 242b116..d7116d8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -23,7 +23,6 @@
 import static com.android.server.am.ActivityManagerService.MY_PID;
 import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED;
 import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING;
-import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
 import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
@@ -556,14 +555,7 @@
                 continue;
             }
             ActivityRecord topActivity = task.getTopActivity();
-            if (topActivity == null) {
-                continue;
-            }
-            // If an activity has just been started it will not yet be visible, but
-            // is expected to be soon. We treat this as if it were already visible.
-            // This ensures a subsequent activity can be started even before this one
-            // becomes visible.
-            if (topActivity.visible || topActivity.isState(INITIALIZING)) {
+            if (topActivity != null && topActivity.visible) {
                 return true;
             }
         }
@@ -620,7 +612,7 @@
                 final ActivityStack stack = mPreQTopResumedActivity.getActivityStack();
                 if (stack != null) {
                     stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
-                            null /* resuming */, false /* pauseImmediately */);
+                            activity);
                 }
             }
             mPreQTopResumedActivity = activity;
diff --git a/services/core/java/com/android/server/wm/WindowProcessListener.java b/services/core/java/com/android/server/wm/WindowProcessListener.java
index 23d7a6a..1dade15 100644
--- a/services/core/java/com/android/server/wm/WindowProcessListener.java
+++ b/services/core/java/com/android/server/wm/WindowProcessListener.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 
 /**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4900869..501a93e 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;
@@ -217,7 +217,8 @@
 import java.util.function.Predicate;
 
 /** A window in the window manager. */
-class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState {
+class WindowState extends WindowContainer<WindowState> implements WindowManagerPolicy.WindowState,
+        InsetsControlTarget {
     static final String TAG = TAG_WITH_CLASS_NAME ? "WindowState" : TAG_WM;
 
     // The minimal size of a window within the usable area of the freeform stack.
@@ -519,11 +520,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.
@@ -754,9 +750,10 @@
         mSeq = seq;
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
-        if (localLOGV) Slog.v(
-            TAG, "Window " + this + " client=" + c.asBinder()
-            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        if (DEBUG) {
+            Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+                            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        }
         try {
             c.asBinder().linkToDeath(deathRecipient, 0);
         } catch (RemoteException e) {
@@ -824,7 +821,7 @@
     }
 
     void attach() {
-        if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
+        if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
         mSession.windowAddedLocked(mAttrs.packageName);
     }
 
@@ -1124,13 +1121,14 @@
             }
         }
 
-        if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
-                "Resolving (mRequestedWidth="
-                + mRequestedWidth + ", mRequestedheight="
-                + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
-                + "): frame=" + mWindowFrames.mFrame.toShortString()
-                + " " + mWindowFrames.getInsetsInfo()
-                + " " + mAttrs.getTitle());
+        if (DEBUG_LAYOUT || DEBUG) {
+            Slog.v(TAG, "Resolving (mRequestedWidth="
+                            + mRequestedWidth + ", mRequestedheight="
+                            + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+                            + "): frame=" + mWindowFrames.mFrame.toShortString()
+                            + " " + mWindowFrames.getInsetsInfo()
+                            + " " + mAttrs.getTitle());
+        }
     }
 
     // TODO: Look into whether this override is still necessary.
@@ -1279,9 +1277,11 @@
         final boolean dragResizingChanged = isDragResizeChanged()
                 && !isDragResizingChangeReported();
 
-        if (localLOGV) Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
-                + " dragResizingChanged=" + dragResizingChanged
-                + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
+        if (DEBUG) {
+            Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+                    + " dragResizingChanged=" + dragResizingChanged
+                    + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
+        }
 
         // We update mLastFrame always rather than in the conditional with the last inset
         // variables, because mFrameSizeChanged only tracks the width and height changing.
@@ -1978,11 +1978,12 @@
         if (startingWindow && DEBUG_STARTING_WINDOW) Slog.d(TAG_WM,
                 "Starting window removed " + this);
 
-        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused())
+        if (DEBUG || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && isFocused()) {
             Slog.v(TAG_WM, "Remove " + this + " client="
-                        + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
-                        + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
-                        + Debug.getCallers(5));
+                    + Integer.toHexString(System.identityHashCode(mClient.asBinder()))
+                    + ", surfaceController=" + mWinAnimator.mSurfaceController + " Callers="
+                    + Debug.getCallers(5));
+        }
 
         final long origId = Binder.clearCallingIdentity();
 
@@ -2007,8 +2008,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.
@@ -3329,7 +3328,8 @@
         }
     }
 
-    void notifyInsetsControlChanged() {
+    @Override
+    public void notifyInsetsControlChanged() {
         final InsetsStateController stateController =
                 getDisplayContent().getInsetsStateController();
         try {
@@ -4375,8 +4375,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;
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ae3a10a..c676e72 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -27,6 +27,7 @@
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -159,7 +160,7 @@
      * window is first added or shown, cleared when the callback has been made. */
     boolean mEnteringAnimation;
 
-    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTmpTransaction;
 
     /** The pixel format of the underlying SurfaceControl */
     int mSurfaceFormat;
@@ -246,6 +247,7 @@
         final WindowManagerService service = win.mWmService;
 
         mService = service;
+        mTmpTransaction = service.mTransactionFactory.get();
         mAnimator = service.mAnimator;
         mPolicy = service.mPolicy;
         mContext = service.mContext;
@@ -269,20 +271,17 @@
         if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.isVisibleByPolicy()) {
             // Upon completion of a not-visible to visible status bar animation a relayout is
             // required.
-            if (displayContent != null) {
-                displayContent.setLayoutNeeded();
-            }
+            displayContent.setLayoutNeeded();
         }
         mWin.onExitAnimationDone();
-        final int displayId = mWin.getDisplayId();
-        int pendingLayoutChanges = FINISH_LAYOUT_REDO_ANIM;
+        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
         if (displayContent.mWallpaperController.isWallpaperTarget(mWin)) {
-            pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
-        mAnimator.setPendingLayoutChanges(displayId, pendingLayoutChanges);
-        if (DEBUG_LAYOUT_REPEATS)
+        if (DEBUG_LAYOUT_REPEATS) {
             mService.mWindowPlacerLocked.debugLayoutRepeats(
-                    "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId));
+                    "WindowStateAnimator", displayContent.pendingLayoutChanges);
+        }
 
         if (mWin.mAppToken != null) {
             mWin.mAppToken.updateReportedVisibilityLocked();
@@ -427,10 +426,6 @@
         }
     }
 
-    private int getLayerStack() {
-        return mWin.getDisplayContent().getDisplay().getLayerStack();
-    }
-
     void resetDrawState() {
         mDrawState = DRAW_PENDING;
 
@@ -539,8 +534,10 @@
             return null;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Got surface: " + mSurfaceController
-                + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top);
+        if (DEBUG) {
+            Slog.v(TAG, "Got surface: " + mSurfaceController
+                    + ", set left=" + w.getFrameLw().left + " top=" + w.getFrameLw().top);
+        }
 
         if (SHOW_LIGHT_TRANSACTIONS) {
             Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
@@ -551,7 +548,7 @@
 
         mLastHidden = true;
 
-        if (WindowManagerService.localLOGV) Slog.v(TAG, "Created surface " + this);
+        if (DEBUG) Slog.v(TAG, "Created surface " + this);
         return mSurfaceController;
     }
 
@@ -744,11 +741,11 @@
                 mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
             }
 
-            if ((DEBUG_ANIM || WindowManagerService.localLOGV)
-                    && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
-                    TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
-                    + " screen=" + (screenAnimation ?
-                            screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            if ((DEBUG_ANIM || DEBUG) && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) {
+                Slog.v(TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+                                + " screen=" + (screenAnimation
+                        ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            }
             return;
         } else if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
             return;
@@ -761,9 +758,10 @@
             return;
         }
 
-        if (WindowManagerService.localLOGV) Slog.v(
-                TAG, "computeShownFrameLocked: " + this +
-                " not attached, mAlpha=" + mAlpha);
+        if (DEBUG) {
+            Slog.v(TAG, "computeShownFrameLocked: " + this
+                    + " not attached, mAlpha=" + mAlpha);
+        }
 
         mShownAlpha = mAlpha;
         mHaveMatrix = false;
@@ -1049,7 +1047,7 @@
         // to prevent further updates until buffer latch.
         // We also need to freeze the Surface geometry until a buffer
         // comes in at the new size (normally position and crop are unfrozen).
-        // setGeometryAppliesWithResizeInTransaction accomplishes this for us.
+        // deferTransactionUntil accomplishes this for us.
         if (wasForceScaled && !mForceScaleUntilResize) {
             mSurfaceController.deferTransactionUntil(mSurfaceController.mSurfaceControl,
                     mWin.getFrameNumber());
@@ -1067,8 +1065,7 @@
 
         if (mSurfaceResized) {
             mReportSurfaceResized = true;
-            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
-                    FINISH_LAYOUT_REDO_WALLPAPER);
+            mWin.getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
         }
     }
 
@@ -1163,16 +1160,16 @@
                         if (mIsWallpaper) {
                             w.dispatchWallpaperVisibility(true);
                         }
-                        if (!w.getDisplayContent().getLastHasContent()) {
+                        final DisplayContent displayContent = w.getDisplayContent();
+                        if (!displayContent.getLastHasContent()) {
                             // This draw means the difference between unique content and mirroring.
                             // Run another pass through performLayout to set mHasContent in the
                             // LogicalDisplay.
-                            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
-                                    FINISH_LAYOUT_REDO_ANIM);
+                            displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
                             if (DEBUG_LAYOUT_REPEATS) {
                                 mService.mWindowPlacerLocked.debugLayoutRepeats(
                                         "showSurfaceRobustlyLocked " + w,
-                                        mAnimator.getPendingLayoutChanges(w.getDisplayId()));
+                                        displayContent.pendingLayoutChanges);
                             }
                         }
                     } else {
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index bcefa8f..6d813d1 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -81,7 +81,7 @@
     private final int mWindowType;
     private final Session mWindowSession;
 
-    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+    private final SurfaceControl.Transaction mTmpTransaction;
 
     public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format,
             int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
@@ -96,6 +96,7 @@
         final WindowState win = animator.mWin;
         mWindowType = windowType;
         mWindowSession = win.mSession;
+        mTmpTransaction = mService.mTransactionFactory.get();
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
         final SurfaceControl.Builder b = win.makeSurface()
@@ -244,10 +245,6 @@
         }
     }
 
-    void setGeometryAppliesWithResizeInTransaction(boolean recoveringMemory) {
-        mSurfaceControl.setGeometryAppliesWithResize();
-    }
-
     void setMatrixInTransaction(float dsdx, float dtdx, float dtdy, float dsdy,
             boolean recoveringMemory) {
         setMatrix(null, dsdx, dtdx, dtdy, dsdy, false);
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index cc791787..56f6d4b 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -25,7 +25,6 @@
 import android.os.Debug;
 import android.os.Trace;
 import android.util.Slog;
-import android.util.SparseIntArray;
 
 import java.io.PrintWriter;
 
@@ -50,8 +49,8 @@
 
     private boolean mTraversalScheduled;
     private int mDeferDepth = 0;
-
-    private final SparseIntArray mTempTransitionReasons = new SparseIntArray();
+    /** The number of layout requests when deferring. */
+    private int mDeferredRequests;
 
     private final Runnable mPerformSurfacePlacement;
 
@@ -65,19 +64,38 @@
     }
 
     /**
-     * See {@link WindowManagerService#deferSurfaceLayout()}
+     * Starts deferring layout passes. Useful when doing multiple changes but to optimize
+     * performance, only one layout pass should be done. This can be called multiple times, and
+     * layouting will be resumed once the last caller has called {@link #continueLayout}.
      */
     void deferLayout() {
         mDeferDepth++;
     }
 
     /**
-     * See {@link WindowManagerService#continueSurfaceLayout()}
+     * Resumes layout passes after deferring them. If there is a deferred direct invocation of
+     * {@link #performSurfacePlacement} ({@link #mDeferredRequests} > 0), when the defer is
+     * done, it will continue to perform layout.
+     *
+     * @param hasChanges Something has changed. That means whether to call
+     *                   {@link #performSurfacePlacement} when {@link #mDeferDepth} becomes zero.
+     * @see #deferLayout
      */
-    void continueLayout() {
+    void continueLayout(boolean hasChanges) {
         mDeferDepth--;
-        if (mDeferDepth <= 0) {
+        if (mDeferDepth > 0) {
+            return;
+        }
+
+        if (hasChanges || mDeferredRequests > 0) {
+            if (DEBUG) {
+                Slog.i(TAG, "continueLayout hasChanges=" + hasChanges
+                        + " deferredRequests=" + mDeferredRequests + " " + Debug.getCallers(2, 3));
+            }
             performSurfacePlacement();
+            mDeferredRequests = 0;
+        } else if (DEBUG) {
+            Slog.i(TAG, "Cancel continueLayout " + Debug.getCallers(2, 3));
         }
     }
 
@@ -97,6 +115,7 @@
 
     final void performSurfacePlacement(boolean force) {
         if (mDeferDepth > 0 && !force) {
+            mDeferredRequests++;
             return;
         }
         int loopCount = 6;
@@ -195,10 +214,19 @@
     }
 
     void requestTraversal() {
-        if (!mTraversalScheduled) {
-            mTraversalScheduled = true;
-            mService.mAnimationHandler.post(mPerformSurfacePlacement);
+        if (mTraversalScheduled) {
+            return;
         }
+
+        // Set as scheduled even the request will be deferred because mDeferredRequests is also
+        // increased, then the end of deferring will perform the request.
+        mTraversalScheduled = true;
+        if (mDeferDepth > 0) {
+            mDeferredRequests++;
+            if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));
+            return;
+        }
+        mService.mAnimationHandler.post(mPerformSurfacePlacement);
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 6218498..d9c7fed 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -57,11 +57,25 @@
     ],
 
     include_dirs: [
-        "bionic/libc/private",
         "frameworks/base/libs",
         "frameworks/native/services",
         "system/gatekeeper/include",
     ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+
+    product_variables: {
+        arc: {
+            exclude_srcs: [
+                "com_android_server_AlarmManagerService.cpp",
+            ],
+            srcs: [
+                ":arctimersrcs",
+            ],
+        }
+    }
 }
 
 cc_defaults {
@@ -98,8 +112,6 @@
         "libGLESv2",
         "libnetutils",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libutils",
         "libhwui",
         "libbpf_android",
diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp
index 159a496..78b64ca 100644
--- a/services/core/jni/com_android_server_SystemServer.cpp
+++ b/services/core/jni/com_android_server_SystemServer.cpp
@@ -24,7 +24,7 @@
 #include <sensorservice/SensorService.h>
 #include <sensorservicehidl/SensorManager.h>
 
-#include <bionic_malloc.h>
+#include <bionic/malloc.h>
 
 #include <cutils/properties.h>
 #include <utils/Log.h>
diff --git a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
index b53ea92..2b1c83f 100644
--- a/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
+++ b/services/core/jni/com_android_server_devicepolicy_CryptoTestHelper.cpp
@@ -22,7 +22,7 @@
 namespace {
 
 static jint runSelfTest(JNIEnv* env, jobject /* clazz */) {
-    return BORINGSSL_self_test();
+    return FIPS_mode();
 }
 
 static const JNINativeMethod methods[] = {
@@ -39,4 +39,4 @@
             env, "com/android/server/devicepolicy/CryptoTestHelper", methods, NELEM(methods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 73bb579..4e04348 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -201,13 +201,8 @@
 sp<ISuspendControlService> getSuspendControl() {
     static std::once_flag suspendControlFlag;
     std::call_once(suspendControlFlag, [](){
-        while(gSuspendControl == nullptr) {
-            sp<IBinder> control =
-                    defaultServiceManager()->getService(String16("suspend_control"));
-            if (control != nullptr) {
-                gSuspendControl = interface_cast<ISuspendControlService>(control);
-            }
-        }
+        gSuspendControl = waitForService<ISuspendControlService>(String16("suspend_control"));
+        LOG_ALWAYS_FATAL_IF(gSuspendControl == nullptr);
     });
     return gSuspendControl;
 }
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 6cd9f2c..9ceb770 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -36,61 +36,33 @@
 #include <linux/fsverity.h>
 #else
 
-// Before fs-verity is upstreamed, use the current snapshot for development.
-// https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git/tree/include/uapi/linux/fsverity.h?h=fsverity
-
 #include <linux/limits.h>
 #include <linux/ioctl.h>
 #include <linux/types.h>
 
+#define FS_VERITY_HASH_ALG_SHA256	1
+
+struct fsverity_enable_arg {
+	__u32 version;
+	__u32 hash_algorithm;
+	__u32 block_size;
+	__u32 salt_size;
+	__u64 salt_ptr;
+	__u32 sig_size;
+	__u32 __reserved1;
+	__u64 sig_ptr;
+	__u64 __reserved2[11];
+};
+
 struct fsverity_digest {
     __u16 digest_algorithm;
     __u16 digest_size; /* input/output */
     __u8 digest[];
 };
 
-#define FS_IOC_ENABLE_VERITY	_IO('f', 133)
+#define FS_IOC_ENABLE_VERITY	_IOW('f', 133, struct fsverity_enable_arg)
 #define FS_IOC_MEASURE_VERITY	_IOWR('f', 134, struct fsverity_digest)
 
-#define FS_VERITY_MAGIC		"FSVerity"
-
-#define FS_VERITY_ALG_SHA256	1
-
-struct fsverity_descriptor {
-    __u8 magic[8];		/* must be FS_VERITY_MAGIC */
-    __u8 major_version;	/* must be 1 */
-    __u8 minor_version;	/* must be 0 */
-    __u8 log_data_blocksize;/* log2(data-bytes-per-hash), e.g. 12 for 4KB */
-    __u8 log_tree_blocksize;/* log2(tree-bytes-per-hash), e.g. 12 for 4KB */
-    __le16 data_algorithm;	/* hash algorithm for data blocks */
-    __le16 tree_algorithm;	/* hash algorithm for tree blocks */
-    __le32 flags;		/* flags */
-    __le32 __reserved1;	/* must be 0 */
-    __le64 orig_file_size;	/* size of the original file data */
-    __le16 auth_ext_count;	/* number of authenticated extensions */
-    __u8 __reserved2[30];	/* must be 0 */
-};
-
-#define FS_VERITY_EXT_ROOT_HASH		1
-#define FS_VERITY_EXT_PKCS7_SIGNATURE	3
-
-struct fsverity_extension {
-    __le32 length;
-    __le16 type;		/* Type of this extension (see codes above) */
-    __le16 __reserved;	/* Reserved, must be 0 */
-};
-
-struct fsverity_digest_disk {
-    __le16 digest_algorithm;
-    __le16 digest_size;
-    __u8 digest[];
-};
-
-struct fsverity_footer {
-    __le32 desc_reverse_offset;	/* distance to fsverity_descriptor */
-    __u8 magic[8];			/* FS_VERITY_MAGIC */
-} __packed;
-
 #endif
 
 const int kSha256Bytes = 32;
@@ -99,52 +71,24 @@
 
 namespace {
 
-class JavaByteArrayHolder {
-  public:
-    JavaByteArrayHolder(const JavaByteArrayHolder &other) = delete;
-    JavaByteArrayHolder(JavaByteArrayHolder &&other)
-          : mEnv(other.mEnv), mBytes(other.mBytes), mElements(other.mElements) {
-        other.mElements = nullptr;
-    }
-
-    static JavaByteArrayHolder newArray(JNIEnv* env, jsize size) {
-        return JavaByteArrayHolder(env, size);
-    }
-
-    jbyte* getRaw() {
-        return mElements;
-    }
-
-    jbyteArray release() {
-        mEnv->ReleaseByteArrayElements(mBytes, mElements, 0);
-        mElements = nullptr;
-        return mBytes;
-    }
-
-    ~JavaByteArrayHolder() {
-        LOG_ALWAYS_FATAL_IF(mElements != nullptr, "Elements are not released");
-    }
-
-  private:
-    JavaByteArrayHolder(JNIEnv* env, jsize size) {
-        mEnv = env;
-        mBytes = mEnv->NewByteArray(size);
-        mElements = mEnv->GetByteArrayElements(mBytes, nullptr);
-        memset(mElements, 0, size);
-    }
-
-    JNIEnv* mEnv;
-    jbyteArray mBytes;
-    jbyte* mElements;
-};
-
-int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) {
+int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) {
     const char* path = env->GetStringUTFChars(filePath, nullptr);
     ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
+    env->ReleaseStringUTFChars(filePath, path);
     if (rfd.get() < 0) {
       return errno;
     }
-    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, nullptr) < 0) {
+
+    fsverity_enable_arg arg = {};
+    arg.version = 1;
+    arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+    arg.block_size = 4096;
+    arg.salt_size = 0;
+    arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
+    arg.sig_size = env->GetArrayLength(signature);
+    arg.sig_ptr = reinterpret_cast<uintptr_t>(signature);
+
+    if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
       return errno;
     }
     return 0;
@@ -159,6 +103,7 @@
 
     const char* path = env->GetStringUTFChars(filePath, nullptr);
     ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
+    env->ReleaseStringUTFChars(filePath, path);
     if (rfd.get() < 0) {
       return errno;
     }
@@ -168,71 +113,9 @@
     return 0;
 }
 
-jbyteArray constructFsveritySignedData(JNIEnv* env, jobject /* clazz */, jbyteArray digest) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_digest_disk) + kSha256Bytes);
-    fsverity_digest_disk* data = reinterpret_cast<fsverity_digest_disk*>(raii.getRaw());
-
-    data->digest_algorithm = FS_VERITY_ALG_SHA256;
-    data->digest_size = kSha256Bytes;
-    if (env->GetArrayLength(digest) != kSha256Bytes) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "Invalid hash size of %d",
-                          env->GetArrayLength(digest));
-        return 0;
-    }
-    const jbyte* src = env->GetByteArrayElements(digest, nullptr);
-    memcpy(data->digest, src, kSha256Bytes);
-
-    return raii.release();
-}
-
-
-jbyteArray constructFsverityDescriptor(JNIEnv* env, jobject /* clazz */, jlong fileSize) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_descriptor));
-    fsverity_descriptor* desc = reinterpret_cast<fsverity_descriptor*>(raii.getRaw());
-
-    memcpy(desc->magic, FS_VERITY_MAGIC, sizeof(desc->magic));
-    desc->major_version = 1;
-    desc->minor_version = 0;
-    desc->log_data_blocksize = 12;
-    desc->log_tree_blocksize = 12;
-    desc->data_algorithm = FS_VERITY_ALG_SHA256;
-    desc->tree_algorithm = FS_VERITY_ALG_SHA256;
-    desc->flags = 0;
-    desc->orig_file_size = fileSize;
-    desc->auth_ext_count = 1;
-
-    return raii.release();
-}
-
-jbyteArray constructFsverityExtension(JNIEnv* env, jobject /* clazz */, jshort extensionId,
-        jint extensionDataSize) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_extension));
-    fsverity_extension* ext = reinterpret_cast<fsverity_extension*>(raii.getRaw());
-
-    ext->length = sizeof(fsverity_extension) + extensionDataSize;
-    ext->type = extensionId;
-
-    return raii.release();
-}
-
-jbyteArray constructFsverityFooter(JNIEnv* env, jobject /* clazz */,
-        jint offsetToDescriptorHead) {
-    auto raii = JavaByteArrayHolder::newArray(env, sizeof(fsverity_footer));
-    fsverity_footer* footer = reinterpret_cast<fsverity_footer*>(raii.getRaw());
-
-    footer->desc_reverse_offset = offsetToDescriptorHead + sizeof(fsverity_footer);
-    memcpy(footer->magic, FS_VERITY_MAGIC, sizeof(footer->magic));
-
-    return raii.release();
-}
-
 const JNINativeMethod sMethods[] = {
-    { "enableFsverityNative", "(Ljava/lang/String;)I", (void *)enableFsverity },
+    { "enableFsverityNative", "(Ljava/lang/String;[B)I", (void *)enableFsverity },
     { "measureFsverityNative", "(Ljava/lang/String;)I", (void *)measureFsverity },
-    { "constructFsveritySignedDataNative", "([B)[B", (void *)constructFsveritySignedData },
-    { "constructFsverityDescriptorNative", "(J)[B", (void *)constructFsverityDescriptor },
-    { "constructFsverityExtensionNative", "(SI)[B", (void *)constructFsverityExtension },
-    { "constructFsverityFooterNative", "(I)[B", (void *)constructFsverityFooter },
 };
 
 }  // namespace
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 478bc88..704c808 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1113,142 +1113,107 @@
             info.writePoliciesToXml(out);
             out.endTag(null, TAG_POLICIES);
             if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
-                out.startTag(null, TAG_PASSWORD_QUALITY);
-                out.attribute(null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.quality));
-                out.endTag(null, TAG_PASSWORD_QUALITY);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
                 if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LENGTH);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.length));
-                    out.endTag(null, TAG_MIN_PASSWORD_LENGTH);
-                }
-                if(passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
-                    out.startTag(null, TAG_PASSWORD_HISTORY_LENGTH);
-                    out.attribute(null, ATTR_VALUE, Integer.toString(passwordHistoryLength));
-                    out.endTag(null, TAG_PASSWORD_HISTORY_LENGTH);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
                 }
                 if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
-                    out.startTag(null, TAG_MIN_PASSWORD_UPPERCASE);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.upperCase));
-                    out.endTag(null, TAG_MIN_PASSWORD_UPPERCASE);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
                 }
                 if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LOWERCASE);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.lowerCase));
-                    out.endTag(null, TAG_MIN_PASSWORD_LOWERCASE);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
                 }
                 if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
-                    out.startTag(null, TAG_MIN_PASSWORD_LETTERS);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.letters));
-                    out.endTag(null, TAG_MIN_PASSWORD_LETTERS);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
                 }
                 if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
-                    out.startTag(null, TAG_MIN_PASSWORD_NUMERIC);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.numeric));
-                    out.endTag(null, TAG_MIN_PASSWORD_NUMERIC);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
                 }
                 if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
-                    out.startTag(null, TAG_MIN_PASSWORD_SYMBOLS);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.symbols));
-                    out.endTag(null, TAG_MIN_PASSWORD_SYMBOLS);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
                 }
                 if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
-                    out.startTag(null, TAG_MIN_PASSWORD_NONLETTER);
-                    out.attribute(
-                            null, ATTR_VALUE, Integer.toString(minimumPasswordMetrics.nonLetter));
-                    out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
+                    writeAttributeValueToXml(
+                            out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
                 }
             }
+            if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_HISTORY_LENGTH, passwordHistoryLength);
+            }
             if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
-                out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
-                out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
-                out.endTag(null, TAG_MAX_TIME_TO_UNLOCK);
+                writeAttributeValueToXml(
+                        out, TAG_MAX_TIME_TO_UNLOCK, maximumTimeToUnlock);
             }
             if (strongAuthUnlockTimeout != DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
-                out.startTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
-                out.attribute(null, ATTR_VALUE, Long.toString(strongAuthUnlockTimeout));
-                out.endTag(null, TAG_STRONG_AUTH_UNLOCK_TIMEOUT);
+                writeAttributeValueToXml(
+                        out, TAG_STRONG_AUTH_UNLOCK_TIMEOUT, strongAuthUnlockTimeout);
             }
             if (maximumFailedPasswordsForWipe != DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
-                out.startTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
-                out.attribute(null, ATTR_VALUE, Integer.toString(maximumFailedPasswordsForWipe));
-                out.endTag(null, TAG_MAX_FAILED_PASSWORD_WIPE);
+                writeAttributeValueToXml(
+                        out, TAG_MAX_FAILED_PASSWORD_WIPE, maximumFailedPasswordsForWipe);
             }
             if (specifiesGlobalProxy) {
-                out.startTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(specifiesGlobalProxy));
-                out.endTag(null, TAG_SPECIFIES_GLOBAL_PROXY);
+                writeAttributeValueToXml(
+                        out, TAG_SPECIFIES_GLOBAL_PROXY, specifiesGlobalProxy);
                 if (globalProxySpec != null) {
-                    out.startTag(null, TAG_GLOBAL_PROXY_SPEC);
-                    out.attribute(null, ATTR_VALUE, globalProxySpec);
-                    out.endTag(null, TAG_GLOBAL_PROXY_SPEC);
+                    writeAttributeValueToXml(out, TAG_GLOBAL_PROXY_SPEC, globalProxySpec);
                 }
                 if (globalProxyExclusionList != null) {
-                    out.startTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
-                    out.attribute(null, ATTR_VALUE, globalProxyExclusionList);
-                    out.endTag(null, TAG_GLOBAL_PROXY_EXCLUSION_LIST);
+                    writeAttributeValueToXml(
+                            out, TAG_GLOBAL_PROXY_EXCLUSION_LIST, globalProxyExclusionList);
                 }
             }
             if (passwordExpirationTimeout != DEF_PASSWORD_EXPIRATION_TIMEOUT) {
-                out.startTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
-                out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationTimeout));
-                out.endTag(null, TAG_PASSWORD_EXPIRATION_TIMEOUT);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_EXPIRATION_TIMEOUT, passwordExpirationTimeout);
             }
             if (passwordExpirationDate != DEF_PASSWORD_EXPIRATION_DATE) {
-                out.startTag(null, TAG_PASSWORD_EXPIRATION_DATE);
-                out.attribute(null, ATTR_VALUE, Long.toString(passwordExpirationDate));
-                out.endTag(null, TAG_PASSWORD_EXPIRATION_DATE);
+                writeAttributeValueToXml(
+                        out, TAG_PASSWORD_EXPIRATION_DATE, passwordExpirationDate);
             }
             if (encryptionRequested) {
-                out.startTag(null, TAG_ENCRYPTION_REQUESTED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(encryptionRequested));
-                out.endTag(null, TAG_ENCRYPTION_REQUESTED);
+                writeAttributeValueToXml(
+                        out, TAG_ENCRYPTION_REQUESTED, encryptionRequested);
             }
             if (testOnlyAdmin) {
-                out.startTag(null, TAG_TEST_ONLY_ADMIN);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(testOnlyAdmin));
-                out.endTag(null, TAG_TEST_ONLY_ADMIN);
+                writeAttributeValueToXml(
+                        out, TAG_TEST_ONLY_ADMIN, testOnlyAdmin);
             }
             if (disableCamera) {
-                out.startTag(null, TAG_DISABLE_CAMERA);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableCamera));
-                out.endTag(null, TAG_DISABLE_CAMERA);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CAMERA, disableCamera);
             }
             if (disableCallerId) {
-                out.startTag(null, TAG_DISABLE_CALLER_ID);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId));
-                out.endTag(null, TAG_DISABLE_CALLER_ID);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CALLER_ID, disableCallerId);
             }
             if (disableContactsSearch) {
-                out.startTag(null, TAG_DISABLE_CONTACTS_SEARCH);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableContactsSearch));
-                out.endTag(null, TAG_DISABLE_CONTACTS_SEARCH);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_CONTACTS_SEARCH, disableContactsSearch);
             }
             if (!disableBluetoothContactSharing) {
-                out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
-                out.attribute(null, ATTR_VALUE,
-                        Boolean.toString(disableBluetoothContactSharing));
-                out.endTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING, disableBluetoothContactSharing);
             }
             if (disableScreenCapture) {
-                out.startTag(null, TAG_DISABLE_SCREEN_CAPTURE);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(disableScreenCapture));
-                out.endTag(null, TAG_DISABLE_SCREEN_CAPTURE);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_SCREEN_CAPTURE, disableScreenCapture);
             }
             if (requireAutoTime) {
-                out.startTag(null, TAG_REQUIRE_AUTO_TIME);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(requireAutoTime));
-                out.endTag(null, TAG_REQUIRE_AUTO_TIME);
+                writeAttributeValueToXml(
+                        out, TAG_REQUIRE_AUTO_TIME, requireAutoTime);
             }
             if (forceEphemeralUsers) {
-                out.startTag(null, TAG_FORCE_EPHEMERAL_USERS);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers));
-                out.endTag(null, TAG_FORCE_EPHEMERAL_USERS);
+                writeAttributeValueToXml(
+                        out, TAG_FORCE_EPHEMERAL_USERS, forceEphemeralUsers);
             }
             if (isNetworkLoggingEnabled) {
                 out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
@@ -1260,15 +1225,13 @@
                 out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
             }
             if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
-                out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
-                out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
-                out.endTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
+                writeAttributeValueToXml(
+                        out, TAG_DISABLE_KEYGUARD_FEATURES, disabledKeyguardFeatures);
             }
             if (!accountTypesWithManagementDisabled.isEmpty()) {
-                out.startTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT);
                 writeAttributeValuesToXml(
-                        out, TAG_ACCOUNT_TYPE, accountTypesWithManagementDisabled);
-                out.endTag(null,  TAG_DISABLE_ACCOUNT_MANAGEMENT);
+                        out, TAG_DISABLE_ACCOUNT_MANAGEMENT, TAG_ACCOUNT_TYPE,
+                        accountTypesWithManagementDisabled);
             }
             if (!trustAgentInfos.isEmpty()) {
                 Set<Entry<String, TrustAgentInfo>> set = trustAgentInfos.entrySet();
@@ -1291,9 +1254,9 @@
                 out.endTag(null, TAG_MANAGE_TRUST_AGENT_FEATURES);
             }
             if (crossProfileWidgetProviders != null && !crossProfileWidgetProviders.isEmpty()) {
-                out.startTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
-                writeAttributeValuesToXml(out, TAG_PROVIDER, crossProfileWidgetProviders);
-                out.endTag(null, TAG_CROSS_PROFILE_WIDGET_PROVIDERS);
+                writeAttributeValuesToXml(
+                        out, TAG_CROSS_PROFILE_WIDGET_PROVIDERS, TAG_PROVIDER,
+                        crossProfileWidgetProviders);
             }
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
@@ -1307,20 +1270,15 @@
                         out, userRestrictions, TAG_USER_RESTRICTIONS);
             }
             if (!defaultEnabledRestrictionsAlreadySet.isEmpty()) {
-                out.startTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
-                writeAttributeValuesToXml(
-                        out, TAG_RESTRICTION, defaultEnabledRestrictionsAlreadySet);
-                out.endTag(null, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS);
+                writeAttributeValuesToXml(out, TAG_DEFAULT_ENABLED_USER_RESTRICTIONS,
+                        TAG_RESTRICTION,
+                        defaultEnabledRestrictionsAlreadySet);
             }
             if (!TextUtils.isEmpty(shortSupportMessage)) {
-                out.startTag(null, TAG_SHORT_SUPPORT_MESSAGE);
-                out.text(shortSupportMessage.toString());
-                out.endTag(null, TAG_SHORT_SUPPORT_MESSAGE);
+                writeTextToXml(out, TAG_SHORT_SUPPORT_MESSAGE, shortSupportMessage.toString());
             }
             if (!TextUtils.isEmpty(longSupportMessage)) {
-                out.startTag(null, TAG_LONG_SUPPORT_MESSAGE);
-                out.text(longSupportMessage.toString());
-                out.endTag(null, TAG_LONG_SUPPORT_MESSAGE);
+                writeTextToXml(out, TAG_LONG_SUPPORT_MESSAGE, longSupportMessage.toString());
             }
             if (parentAdmin != null) {
                 out.startTag(null, TAG_PARENT_ADMIN);
@@ -1328,29 +1286,20 @@
                 out.endTag(null, TAG_PARENT_ADMIN);
             }
             if (organizationColor != DEF_ORGANIZATION_COLOR) {
-                out.startTag(null, TAG_ORGANIZATION_COLOR);
-                out.attribute(null, ATTR_VALUE, Integer.toString(organizationColor));
-                out.endTag(null, TAG_ORGANIZATION_COLOR);
+                writeAttributeValueToXml(out, TAG_ORGANIZATION_COLOR, organizationColor);
             }
             if (organizationName != null) {
-                out.startTag(null, TAG_ORGANIZATION_NAME);
-                out.text(organizationName);
-                out.endTag(null, TAG_ORGANIZATION_NAME);
+                writeTextToXml(out, TAG_ORGANIZATION_NAME, organizationName);
             }
             if (isLogoutEnabled) {
-                out.startTag(null, TAG_IS_LOGOUT_ENABLED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled));
-                out.endTag(null, TAG_IS_LOGOUT_ENABLED);
+                writeAttributeValueToXml(
+                        out, TAG_IS_LOGOUT_ENABLED, isLogoutEnabled);
             }
             if (startUserSessionMessage != null) {
-                out.startTag(null, TAG_START_USER_SESSION_MESSAGE);
-                out.text(startUserSessionMessage);
-                out.endTag(null, TAG_START_USER_SESSION_MESSAGE);
+                writeTextToXml(out, TAG_START_USER_SESSION_MESSAGE, startUserSessionMessage);
             }
             if (endUserSessionMessage != null) {
-                out.startTag(null, TAG_END_USER_SESSION_MESSAGE);
-                out.text(endUserSessionMessage);
-                out.endTag(null, TAG_END_USER_SESSION_MESSAGE);
+                writeTextToXml(out, TAG_END_USER_SESSION_MESSAGE, endUserSessionMessage);
             }
             if (mCrossProfileCalendarPackages == null) {
                 out.startTag(null, TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL);
@@ -1361,25 +1310,58 @@
             }
         }
 
+        void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
+            out.startTag(null, tag);
+            out.text(text);
+            out.endTag(null, tag);
+        }
+
         void writePackageListToXml(XmlSerializer out, String outerTag,
                 List<String> packageList)
                 throws IllegalArgumentException, IllegalStateException, IOException {
             if (packageList == null) {
                 return;
             }
-
-            out.startTag(null, outerTag);
-            writeAttributeValuesToXml(out, TAG_PACKAGE_LIST_ITEM, packageList);
-            out.endTag(null, outerTag);
+            writeAttributeValuesToXml(out, outerTag, TAG_PACKAGE_LIST_ITEM, packageList);
         }
 
-        void writeAttributeValuesToXml(XmlSerializer out, String tag,
+        void writeAttributeValueToXml(XmlSerializer out, String tag, String value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, value);
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, int value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Integer.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, long value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Long.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValueToXml(XmlSerializer out, String tag, boolean value)
+                throws IOException {
+            out.startTag(null, tag);
+            out.attribute(null, ATTR_VALUE, Boolean.toString(value));
+            out.endTag(null, tag);
+        }
+
+        void writeAttributeValuesToXml(XmlSerializer out, String outerTag, String innerTag,
                 @NonNull Collection<String> values) throws IOException {
+            out.startTag(null, outerTag);
             for (String value : values) {
-                out.startTag(null, tag);
+                out.startTag(null, innerTag);
                 out.attribute(null, ATTR_VALUE, value);
-                out.endTag(null, tag);
+                out.endTag(null, innerTag);
             }
+            out.endTag(null, outerTag);
         }
 
         void readFromXml(XmlPullParser parser, boolean shouldOverridePolicies)
@@ -1999,6 +1981,10 @@
             return LocalServices.getService(LockSettingsInternal.class);
         }
 
+        boolean hasUserSetupCompleted(DevicePolicyData userData) {
+            return userData.mUserSetupComplete;
+        }
+
         boolean isBuildDebuggable() {
             return Build.IS_DEBUGGABLE;
         }
@@ -5659,7 +5645,7 @@
                     KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid));
             try {
                 IKeyChainService keyChain = keyChainConnection.getService();
-                if (!keyChain.installKeyPair(privKey, cert, chain, alias)) {
+                if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
                     return false;
                 }
                 if (requestAccess) {
@@ -8271,7 +8257,7 @@
         if (!mHasFeature) {
             return true;
         }
-        return getUserData(userHandle).mUserSetupComplete;
+        return mInjector.hasUserSetupCompleted(getUserData(userHandle));
     }
 
     private boolean hasPaired(int userHandle) {
diff --git a/services/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java
index 7333f58..62f2c35 100644
--- a/services/net/java/android/net/ConnectivityModuleConnector.java
+++ b/services/net/java/android/net/ConnectivityModuleConnector.java
@@ -38,6 +38,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -76,8 +77,17 @@
     private final SharedLog mLog = new SharedLog(TAG);
     @GuardedBy("mHealthListeners")
     private final ArraySet<ConnectivityModuleHealthListener> mHealthListeners = new ArraySet<>();
+    @NonNull
+    private final Dependencies mDeps;
 
-    private ConnectivityModuleConnector() { }
+    private ConnectivityModuleConnector() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    ConnectivityModuleConnector(@NonNull Dependencies deps) {
+        mDeps = deps;
+    }
 
     /**
      * Get the {@link ConnectivityModuleConnector} singleton instance.
@@ -124,6 +134,59 @@
         void onModuleServiceConnected(@NonNull IBinder iBinder);
     }
 
+    /**
+     * Interface used to determine the intent to use to bind to the module. Useful for testing.
+     */
+    @VisibleForTesting
+    protected interface Dependencies {
+        /**
+         * Determine the intent to use to bind to the module.
+         * @return null if the intent could not be resolved, the intent otherwise.
+         */
+        @Nullable
+        Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess);
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Nullable
+        @Override
+        public Intent getModuleServiceIntent(
+                @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
+                @NonNull String servicePermissionName, boolean inSystemProcess) {
+            final Intent intent =
+                    new Intent(inSystemProcess
+                            ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
+                            : serviceIntentBaseAction);
+            final ComponentName comp = intent.resolveSystemService(pm, 0);
+            if (comp == null) {
+                return null;
+            }
+            intent.setComponent(comp);
+
+            final int uid;
+            try {
+                uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new SecurityException(
+                        "Could not check network stack UID; package not found.", e);
+            }
+
+            final int expectedUid =
+                    inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
+            if (uid != expectedUid) {
+                throw new SecurityException("Invalid network stack UID: " + uid);
+            }
+
+            if (!inSystemProcess) {
+                checkModuleServicePermission(pm, comp, servicePermissionName);
+            }
+
+            return intent;
+        }
+    }
+
 
     /**
      * Add a {@link ConnectivityModuleHealthListener} to listen to network stack health events.
@@ -156,13 +219,13 @@
         final PackageManager pm = mContext.getPackageManager();
 
         // Try to bind in-process if the device was shipped with an in-process version
-        Intent intent = getModuleServiceIntent(pm, serviceIntentBaseAction, servicePermissionName,
-                true /* inSystemProcess */);
+        Intent intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                servicePermissionName, true /* inSystemProcess */);
 
         // Otherwise use the updatable module version
         if (intent == null) {
-            intent = getModuleServiceIntent(pm, serviceIntentBaseAction, servicePermissionName,
-                false /* inSystemProcess */);
+            intent = mDeps.getModuleServiceIntent(pm, serviceIntentBaseAction,
+                    servicePermissionName, false /* inSystemProcess */);
             log("Starting networking module in network_stack process");
         } else {
             log("Starting networking module in system_server process");
@@ -219,41 +282,7 @@
         }
     }
 
-    @Nullable
-    private Intent getModuleServiceIntent(
-            @NonNull PackageManager pm, @NonNull String serviceIntentBaseAction,
-            @NonNull String servicePermissionName, boolean inSystemProcess) {
-        final Intent intent =
-                new Intent(inSystemProcess
-                        ? serviceIntentBaseAction + IN_PROCESS_SUFFIX
-                        : serviceIntentBaseAction);
-        final ComponentName comp = intent.resolveSystemService(pm, 0);
-        if (comp == null) {
-            return null;
-        }
-        intent.setComponent(comp);
-
-        int uid = -1;
-        try {
-            uid = pm.getPackageUidAsUser(comp.getPackageName(), UserHandle.USER_SYSTEM);
-        } catch (PackageManager.NameNotFoundException e) {
-            logWtf("Networking module package not found", e);
-            // Fall through
-        }
-
-        final int expectedUid = inSystemProcess ? Process.SYSTEM_UID : Process.NETWORK_STACK_UID;
-        if (uid != expectedUid) {
-            throw new SecurityException("Invalid network stack UID: " + uid);
-        }
-
-        if (!inSystemProcess) {
-            checkModuleServicePermission(pm, comp, servicePermissionName);
-        }
-
-        return intent;
-    }
-
-    private void checkModuleServicePermission(
+    private static void checkModuleServicePermission(
             @NonNull PackageManager pm, @NonNull ComponentName comp,
             @NonNull String servicePermissionName) {
         final int hasPermission =
diff --git a/services/net/java/android/net/NetworkStackClient.java b/services/net/java/android/net/NetworkStackClient.java
index abb4666..69e2406 100644
--- a/services/net/java/android/net/NetworkStackClient.java
+++ b/services/net/java/android/net/NetworkStackClient.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -51,6 +52,9 @@
     private static NetworkStackClient sInstance;
 
     @NonNull
+    private final Dependencies mDependencies;
+
+    @NonNull
     @GuardedBy("mPendingNetStackRequests")
     private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
     @Nullable
@@ -66,7 +70,50 @@
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
 
-    private NetworkStackClient() { }
+    @VisibleForTesting
+    protected NetworkStackClient(@NonNull Dependencies dependencies) {
+        mDependencies = dependencies;
+    }
+
+    private NetworkStackClient() {
+        this(new DependenciesImpl());
+    }
+
+    @VisibleForTesting
+    protected interface Dependencies {
+        void addToServiceManager(@NonNull IBinder service);
+        void checkCallerUid();
+        ConnectivityModuleConnector getConnectivityModuleConnector();
+    }
+
+    private static class DependenciesImpl implements Dependencies {
+        @Override
+        public void addToServiceManager(@NonNull IBinder service) {
+            ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service,
+                    false /* allowIsolated */, DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        }
+
+        @Override
+        public void checkCallerUid() {
+            final int caller = Binder.getCallingUid();
+            // This is a client lib so "caller" is the current UID in most cases. The check is done
+            // here in the caller's process just to provide a nicer error message to clients; more
+            // generic checks are also done in NetworkStackService.
+            // See PermissionUtil in NetworkStack for the actual check on the service side - the
+            // checks here should be kept in sync with PermissionUtil.
+            if (caller != Process.SYSTEM_UID
+                    && caller != Process.NETWORK_STACK_UID
+                    && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)) {
+                throw new SecurityException(
+                        "Only the system server should try to bind to the network stack.");
+            }
+        }
+
+        @Override
+        public ConnectivityModuleConnector getConnectivityModuleConnector() {
+            return ConnectivityModuleConnector.getInstance();
+        }
+    }
 
     /**
      * Get the NetworkStackClient singleton instance.
@@ -150,9 +197,7 @@
 
     private void registerNetworkStackService(@NonNull IBinder service) {
         final INetworkStackConnector connector = INetworkStackConnector.Stub.asInterface(service);
-
-        ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
-                DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+        mDependencies.addToServiceManager(service);
         log("Network stack service registered");
 
         final ArrayList<NetworkStackCallback> requests;
@@ -185,7 +230,7 @@
      * started.
      */
     public void start() {
-        ConnectivityModuleConnector.getInstance().startModuleService(
+        mDependencies.getConnectivityModuleConnector().startModuleService(
                 INetworkStackConnector.class.getName(), PERMISSION_MAINLINE_NETWORK_STACK,
                 new NetworkStackConnection());
         log("Network stack service start requested");
@@ -251,16 +296,7 @@
     }
 
     private void requestConnector(@NonNull NetworkStackCallback request) {
-        // TODO: PID check.
-        final int caller = Binder.getCallingUid();
-        if (caller != Process.SYSTEM_UID
-                && caller != Process.NETWORK_STACK_UID
-                && !UserHandle.isSameApp(caller, Process.BLUETOOTH_UID)
-                && !UserHandle.isSameApp(caller, Process.PHONE_UID)) {
-            // Don't even attempt to obtain the connector and give a nice error message
-            throw new SecurityException(
-                    "Only the system server should try to bind to the network stack.");
-        }
+        mDependencies.checkCallerUid();
 
         if (!mWasSystemServerInitialized) {
             // The network stack is not being started in this process, e.g. this process is not
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
deleted file mode 100644
index 24e5573..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.util.Preconditions;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkListingMapTest {
-    private static final String CHUNK_A = "CHUNK_A";
-    private static final String CHUNK_B = "CHUNK_B";
-    private static final String CHUNK_C = "CHUNK_C";
-
-    private static final int CHUNK_A_LENGTH = 256;
-    private static final int CHUNK_B_LENGTH = 1024;
-    private static final int CHUNK_C_LENGTH = 4055;
-
-    private ChunkHash mChunkHashA;
-    private ChunkHash mChunkHashB;
-    private ChunkHash mChunkHashC;
-
-    @Before
-    public void setUp() throws Exception {
-        mChunkHashA = getHash(CHUNK_A);
-        mChunkHashB = getHash(CHUNK_B);
-        mChunkHashC = getHash(CHUNK_C);
-    }
-
-    @Test
-    public void testHasChunk_whenChunkInListing_returnsTrue() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        boolean chunkAInList = chunkListingMap.hasChunk(mChunkHashA);
-        boolean chunkBInList = chunkListingMap.hasChunk(mChunkHashB);
-        boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
-
-        assertThat(chunkAInList).isTrue();
-        assertThat(chunkBInList).isTrue();
-        assertThat(chunkCInList).isTrue();
-    }
-
-    @Test
-    public void testHasChunk_whenChunkNotInListing_returnsFalse() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-        ChunkHash chunkHashEmpty = getHash("");
-
-        boolean chunkCInList = chunkListingMap.hasChunk(mChunkHashC);
-        boolean emptyChunkInList = chunkListingMap.hasChunk(chunkHashEmpty);
-
-        assertThat(chunkCInList).isFalse();
-        assertThat(emptyChunkInList).isFalse();
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsEntryWithCorrectLength() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
-        ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
-        ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(entryA.getLength()).isEqualTo(CHUNK_A_LENGTH);
-        assertThat(entryB.getLength()).isEqualTo(CHUNK_B_LENGTH);
-        assertThat(entryC.getLength()).isEqualTo(CHUNK_C_LENGTH);
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsEntryWithCorrectStart() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry entryA = chunkListingMap.getChunkEntry(mChunkHashA);
-        ChunkListingMap.Entry entryB = chunkListingMap.getChunkEntry(mChunkHashB);
-        ChunkListingMap.Entry entryC = chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(entryA.getStart()).isEqualTo(0);
-        assertThat(entryB.getStart()).isEqualTo(CHUNK_A_LENGTH);
-        assertThat(entryC.getStart()).isEqualTo(CHUNK_A_LENGTH + CHUNK_B_LENGTH);
-    }
-
-    @Test
-    public void testGetChunkEntry_returnsNullForNonExistentChunk() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH});
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        ChunkListingMap.Entry chunkEntryNonexistentChunk =
-                chunkListingMap.getChunkEntry(mChunkHashC);
-
-        assertThat(chunkEntryNonexistentChunk).isNull();
-    }
-
-    @Test
-    public void testReadFromProto_whenEmptyProto_returnsChunkListingMapWith0Chunks()
-            throws Exception {
-        ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
-        ChunkListingMap chunkListingMap = ChunkListingMap.readFromProto(emptyProto);
-
-        assertThat(chunkListingMap.getChunkCount()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_returnsChunkListingWithCorrectSize() throws Exception {
-        byte[] chunkListingProto =
-                createChunkListingProto(
-                        new ChunkHash[] {mChunkHashA, mChunkHashB, mChunkHashC},
-                        new int[] {CHUNK_A_LENGTH, CHUNK_B_LENGTH, CHUNK_C_LENGTH});
-
-        ChunkListingMap chunkListingMap =
-                ChunkListingMap.readFromProto(
-                        new ProtoInputStream(new ByteArrayInputStream(chunkListingProto)));
-
-        assertThat(chunkListingMap.getChunkCount()).isEqualTo(3);
-    }
-
-    private byte[] createChunkListingProto(ChunkHash[] hashes, int[] lengths) {
-        Preconditions.checkArgument(hashes.length == lengths.length);
-        ProtoOutputStream outputStream = new ProtoOutputStream();
-
-        for (int i = 0; i < hashes.length; ++i) {
-            writeToProtoOutputStream(outputStream, hashes[i], lengths[i]);
-        }
-        outputStream.flush();
-
-        return outputStream.getBytes();
-    }
-
-    private void writeToProtoOutputStream(ProtoOutputStream out, ChunkHash chunkHash, int length) {
-        long token = out.start(ChunksMetadataProto.ChunkListing.CHUNKS);
-        out.write(ChunksMetadataProto.Chunk.HASH, chunkHash.getHash());
-        out.write(ChunksMetadataProto.Chunk.LENGTH, length);
-        out.end(token);
-    }
-
-    private ChunkHash getHash(String name) {
-        return new ChunkHash(
-                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
-    }
-}
diff --git a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java b/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java
deleted file mode 100644
index 17c9a86..0000000
--- a/services/robotests/backup/src/com/android/server/backup/encryption/chunk/ChunkTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.backup.encryption.chunk;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.google.common.base.Charsets;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-import java.io.ByteArrayInputStream;
-import java.util.Arrays;
-
-@RunWith(RobolectricTestRunner.class)
-@Presubmit
-public class ChunkTest {
-    private static final String CHUNK_A = "CHUNK_A";
-    private static final int CHUNK_A_LENGTH = 256;
-
-    private ChunkHash mChunkHashA;
-
-    @Before
-    public void setUp() throws Exception {
-        mChunkHashA = getHash(CHUNK_A);
-    }
-
-    @Test
-    public void testReadFromProto_readsCorrectly() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    @Test
-    public void testReadFromProto_whenFieldsWrittenInReversedOrder_readsCorrectly()
-            throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        // Write fields of Chunk proto in reverse order.
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    @Test
-    public void testReadFromProto_whenEmptyProto_returnsEmptyHash() throws Exception {
-        ProtoInputStream emptyProto = new ProtoInputStream(new ByteArrayInputStream(new byte[] {}));
-
-        Chunk chunk = Chunk.readFromProto(emptyProto);
-
-        assertThat(chunk.getHash()).asList().hasSize(0);
-        assertThat(chunk.getLength()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_whenOnlyHashSet_returnsChunkWithOnlyHash() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.HASH, mChunkHashA.getHash());
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(mChunkHashA.getHash());
-        assertThat(chunk.getLength()).isEqualTo(0);
-    }
-
-    @Test
-    public void testReadFromProto_whenOnlyLengthSet_returnsChunkWithOnlyLength() throws Exception {
-        ProtoOutputStream out = new ProtoOutputStream();
-        out.write(ChunksMetadataProto.Chunk.LENGTH, CHUNK_A_LENGTH);
-        out.flush();
-        byte[] protoBytes = out.getBytes();
-
-        Chunk chunk =
-                Chunk.readFromProto(new ProtoInputStream(new ByteArrayInputStream(protoBytes)));
-
-        assertThat(chunk.getHash()).isEqualTo(new byte[] {});
-        assertThat(chunk.getLength()).isEqualTo(CHUNK_A_LENGTH);
-    }
-
-    private ChunkHash getHash(String name) {
-        return new ChunkHash(
-                Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES));
-    }
-}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index ad94e61..8699669 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -25,6 +25,7 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "truth-prebuilt",
+        "testables",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 9670899..1685b04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -1486,6 +1486,45 @@
                 SCHED_GROUP_DEFAULT);
     }
 
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testUpdateOomAdj_DoAll_ServiceB() {
+        ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+                MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
+        ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
+                MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        long now = SystemClock.uptimeMillis();
+        ServiceRecord s = bindService(app, app2, null, 0, mock(IBinder.class));
+        s.startRequested = true;
+        s.lastActivity = now;
+        s = bindService(app2, app, null, 0, mock(IBinder.class));
+        s.startRequested = true;
+        s.lastActivity = now;
+        ProcessRecord app3 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID,
+                MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false));
+        s = mock(ServiceRecord.class);
+        s.app = app3;
+        setFieldValue(ServiceRecord.class, s, "connections",
+                new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
+        app3.services.add(s);
+        doCallRealMethod().when(s).getConnections();
+        s.startRequested = true;
+        s.lastActivity = now;
+        ArrayList<ProcessRecord> lru = sService.mProcessList.mLruProcesses;
+        lru.clear();
+        lru.add(app3);
+        lru.add(app2);
+        lru.add(app);
+        sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
+        sService.mOomAdjuster.mNumServiceProcs = 3;
+        sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+        lru.clear();
+
+        assertEquals(SERVICE_B_ADJ, app3.setAdj);
+        assertEquals(SERVICE_ADJ, app2.setAdj);
+        assertEquals(SERVICE_ADJ, app.setAdj);
+    }
+
     private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
             String packageName, boolean hasShownUi) {
         long now = SystemClock.uptimeMillis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
new file mode 100644
index 0000000..307092d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
+
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.WallpaperManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ServiceInfo;
+import android.hardware.display.DisplayManager;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.service.wallpaper.IWallpaperConnection;
+import android.service.wallpaper.WallpaperService;
+import android.testing.TestableContext;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.R;
+import com.android.server.LocalServices;
+import com.android.server.wallpaper.WallpaperManagerService.WallpaperData;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Tests for the {@link WallpaperManagerService} class.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:WallpaperManagerServiceTests
+ */
+@Presubmit
+@FlakyTest(bugId = 129797242)
+@RunWith(AndroidJUnit4.class)
+public class WallpaperManagerServiceTests {
+    private static final int DISPLAY_SIZE_DIMENSION = 100;
+    private static StaticMockitoSession sMockitoSession;
+
+    @ClassRule
+    public static final TestableContext sContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+    private static ComponentName sImageWallpaperComponentName;
+    private static ComponentName sDefaultWallpaperComponent;
+
+    private IPackageManager mIpm = AppGlobals.getPackageManager();
+
+    @Mock
+    private DisplayManager mDisplayManager;
+
+    @Rule
+    public final TemporaryFolder mFolder = new TemporaryFolder();
+    private final SparseArray<File> mTempDirs = new SparseArray<>();
+    private WallpaperManagerService mService;
+
+    @BeforeClass
+    public static void setUpClass() {
+        sMockitoSession = mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(LocalServices.class)
+                .spyStatic(WallpaperManager.class)
+                .startMocking();
+
+        final WindowManagerInternal dmi = mock(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, dmi);
+
+        sContext.addMockSystemService(Context.APP_OPS_SERVICE, mock(AppOpsManager.class));
+
+        spyOn(sContext);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+                PackageManager.PERMISSION_GRANTED);
+        sContext.getTestablePermissions().setPermission(
+                android.Manifest.permission.SET_WALLPAPER,
+                PackageManager.PERMISSION_GRANTED);
+        doNothing().when(sContext).sendBroadcastAsUser(any(), any());
+
+        //Wallpaper components
+        final IWallpaperConnection.Stub wallpaperService = mock(IWallpaperConnection.Stub.class);
+        sImageWallpaperComponentName = ComponentName.unflattenFromString(
+                sContext.getResources().getString(R.string.image_wallpaper_component));
+        // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
+        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
+
+        if (sDefaultWallpaperComponent == null) {
+            sDefaultWallpaperComponent = sImageWallpaperComponentName;
+            doReturn(sImageWallpaperComponentName).when(() ->
+                    WallpaperManager.getDefaultWallpaperComponent(any()));
+        } else {
+            sContext.addMockService(sDefaultWallpaperComponent, wallpaperService);
+        }
+
+        sContext.addMockService(sImageWallpaperComponentName, wallpaperService);
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (sMockitoSession != null) {
+            sMockitoSession.finishMocking();
+            sMockitoSession = null;
+        }
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        sImageWallpaperComponentName = null;
+        sDefaultWallpaperComponent = null;
+        reset(sContext);
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+
+        final Display mockDisplay = mock(Display.class);
+        doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
+        doReturn(mockDisplay).when(mDisplayManager).getDisplay(anyInt());
+
+        final Display[] displays = new Display[]{mockDisplay};
+        doReturn(displays).when(mDisplayManager).getDisplays();
+
+        spyOn(mIpm);
+        mService = new TestWallpaperManagerService(sContext);
+        spyOn(mService);
+        mService.systemReady();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WallpaperManagerInternal.class);
+
+        mTempDirs.clear();
+        reset(mIpm);
+        mService = null;
+    }
+
+    protected class TestWallpaperManagerService extends WallpaperManagerService {
+        private static final String TAG = "TestWallpaperManagerService";
+
+        TestWallpaperManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        File getWallpaperDir(int userId) {
+            File tempDir = mTempDirs.get(userId);
+            if (tempDir == null) {
+                try {
+                    tempDir = mFolder.newFolder(String.valueOf(userId));
+                    mTempDirs.append(userId, tempDir);
+                } catch (IOException e) {
+                    Log.e(TAG, "getWallpaperDir failed at userId= " + userId);
+                }
+            }
+            return tempDir;
+        }
+
+        // Always return true for test
+        @Override
+        public boolean isWallpaperSupported(String callingPackage) {
+            return true;
+        }
+
+        // Always return true for test
+        @Override
+        public boolean isSetWallpaperAllowed(String callingPackage) {
+            return true;
+        }
+    }
+
+    /**
+     * Tests that internal basic data should be correct after boot up.
+     */
+    @Test
+    public void testDataCorrectAfterBoot() {
+        mService.switchUser(UserHandle.USER_SYSTEM, null);
+
+        final WallpaperData fallbackData = mService.mFallbackWallpaper;
+        assertEquals("Fallback wallpaper component should be ImageWallpaper.",
+                sImageWallpaperComponentName, fallbackData.wallpaperComponent);
+
+        verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+        verifyDisplayData();
+    }
+
+    /**
+     * Tests setWallpaperComponent and clearWallpaper should work as expected.
+     */
+    @Test
+    public void testSetThenClearComponent() {
+        // Skip if there is no pre-defined default wallpaper component.
+        assumeThat(sDefaultWallpaperComponent,
+                not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+        final int testUserId = UserHandle.USER_SYSTEM;
+        mService.switchUser(testUserId, null);
+        verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
+        verifyCurrentSystemData(testUserId);
+
+        mService.setWallpaperComponent(sImageWallpaperComponentName);
+        verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+        verifyCurrentSystemData(testUserId);
+
+        mService.clearWallpaper(null, FLAG_SYSTEM, testUserId);
+        verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
+        verifyCurrentSystemData(testUserId);
+    }
+
+    /**
+     * Tests internal data should be correct and no crash after switch user continuously.
+     */
+    @Test
+    public void testSwitchMultipleUsers() throws Exception {
+        final int lastUserId = 5;
+        final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
+                PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
+        doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
+
+        final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        final ParceledListSlice ris =
+                mIpm.queryIntentServices(intent,
+                        intent.resolveTypeIfNeeded(sContext.getContentResolver()),
+                        PackageManager.GET_META_DATA, 0);
+        doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), anyInt());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
+                eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
+
+        for (int userId = 0; userId <= lastUserId; userId++) {
+            mService.switchUser(userId, null);
+            verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
+            verifyCurrentSystemData(userId);
+        }
+        verifyNoConnectionBeforeLastUser(lastUserId);
+    }
+
+    /**
+     * Tests internal data should be correct and no crash after switch user + unlock user
+     * continuously.
+     * Simulating that the selected WallpaperService is not built-in. After switching users, the
+     * service should not be bound, but bound to the image wallpaper. After receiving the user
+     * unlock callback and can find the selected service for the user, the selected service should
+     * be bound.
+     */
+    @Test
+    public void testSwitchThenUnlockMultipleUsers() throws Exception {
+        final int lastUserId = 5;
+        final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
+                PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
+        doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
+
+        final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        final ParceledListSlice ris =
+                mIpm.queryIntentServices(intent,
+                        intent.resolveTypeIfNeeded(sContext.getContentResolver()),
+                        PackageManager.GET_META_DATA, 0);
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
+                eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
+
+        for (int userId = 1; userId <= lastUserId; userId++) {
+            mService.switchUser(userId, null);
+            verifyLastWallpaperData(userId, sImageWallpaperComponentName);
+            // Simulate user unlocked
+            doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), eq(userId));
+            mService.onUnlockUser(userId);
+            verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
+            verifyCurrentSystemData(userId);
+        }
+        verifyNoConnectionBeforeLastUser(lastUserId);
+        verifyDisplayData();
+    }
+
+    // Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
+    // non-current user must not bind to wallpaper service.
+    private void verifyNoConnectionBeforeLastUser(int lastUserId) {
+        for (int i = 0; i < lastUserId; i++) {
+            final WallpaperData userData = mService.getCurrentWallpaperData(FLAG_SYSTEM, i);
+            assertNull("No user data connection left", userData.connection);
+        }
+    }
+
+    private void verifyLastWallpaperData(int lastUserId, ComponentName expectedComponent) {
+        final WallpaperData lastData = mService.mLastWallpaper;
+        assertNotNull("Last wallpaper must not be null", lastData);
+        assertEquals("Last wallpaper component must be equals.", expectedComponent,
+                lastData.wallpaperComponent);
+        assertEquals("The user id in last wallpaper should be the last switched user",
+                lastUserId, lastData.userId);
+        assertNotNull("Must exist user data connection on last wallpaper data",
+                lastData.connection);
+    }
+
+    private void verifyCurrentSystemData(int userId) {
+        final WallpaperData lastData = mService.mLastWallpaper;
+        final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, userId);
+        assertEquals("Last wallpaper should be equals to current system wallpaper",
+                lastData, wallpaper);
+    }
+
+    private void verifyDisplayData() {
+        mService.forEachDisplayData(data -> {
+            assertTrue("Display width must larger than maximum screen size",
+                    data.mWidth >= DISPLAY_SIZE_DIMENSION);
+            assertTrue("Display height must larger than maximum screen size",
+                    data.mHeight >= DISPLAY_SIZE_DIMENSION);
+        });
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 356423a..73dcb98 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -111,6 +111,8 @@
     private static final String VIEW_TEXT = "test_view_text";
     private static final int WINDOWID = 12;
     private static final int PIP_WINDOWID = 13;
+    private static final int WINDOWID_ONSECONDDISPLAY = 14;
+    private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
     private static final int SERVICE_ID = 42;
     private static final int A11Y_SERVICE_CAPABILITY = CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
             | CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
@@ -135,6 +137,7 @@
     private AbstractAccessibilityServiceConnection mServiceConnection;
     private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
     private final List<AccessibilityWindowInfo> mA11yWindowInfos = new ArrayList<>();
+    private final List<AccessibilityWindowInfo> mA11yWindowInfosOnSecondDisplay = new ArrayList<>();
     private Callable[] mFindA11yNodesFunctions;
     private Callable<Boolean> mPerformA11yAction;
 
@@ -177,14 +180,22 @@
         when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
 
         // Fake a11yWindowInfo and remote a11y connection for tests.
-        addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false);
-        addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true);
+        addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY);
+        addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY);
+        addA11yWindowInfo(mA11yWindowInfosOnSecondDisplay, WINDOWID_ONSECONDDISPLAY, false,
+                SECONDARY_DISPLAY_ID);
         when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY))
                 .thenReturn(mA11yWindowInfos);
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(0));
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID))
                 .thenReturn(mA11yWindowInfos.get(1));
+        when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID,
+            WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID);
+        when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID))
+            .thenReturn(mA11yWindowInfosOnSecondDisplay);
+        when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID_ONSECONDDISPLAY))
+            .thenReturn(mA11yWindowInfosOnSecondDisplay.get(0));
         final RemoteAccessibilityConnection conn = getRemoteA11yConnection(
                 WINDOWID, mMockIA11yInteractionConnection, PACKAGE_NAME1);
         final RemoteAccessibilityConnection connPip = getRemoteA11yConnection(
@@ -327,6 +338,12 @@
     }
 
     @Test
+    public void getWindow_onNonDefaultDisplay() {
+        assertThat(mServiceConnection.getWindow(WINDOWID_ONSECONDDISPLAY),
+                is(mA11yWindowInfosOnSecondDisplay.get(0)));
+    }
+
+    @Test
     public void accessAccessibilityNodeInfo_whenCantGetInfo_returnNullOrFalse()
             throws Exception {
         when(mMockSecurityPolicy.canGetAccessibilityNodeInfoLocked(
@@ -674,9 +691,10 @@
     }
 
     private AccessibilityWindowInfo addA11yWindowInfo(List<AccessibilityWindowInfo> infos,
-            int windowId, boolean isPip) {
+            int windowId, boolean isPip, int displayId) {
         final AccessibilityWindowInfo info = AccessibilityWindowInfo.obtain();
         info.setId(windowId);
+        info.setDisplayId(displayId);
         info.setPictureInPicture(isPip);
         infos.add(info);
         return info;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
index e72e460..c73be6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
@@ -35,6 +35,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.Consumer;
+
 /**
  * Tests for GlobalActionPerformer
  */
@@ -84,6 +86,6 @@
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
         verify(mMockScreenshotHelper).takeScreenshot(
                 eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
-                anyBoolean(), any(Handler.class));
+                anyBoolean(), any(Handler.class), any());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/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/appop/AppOpsUpgradeTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 66d2bab..70650de 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -89,13 +89,11 @@
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
         for(int i = 0; i < uidStates.size(); i++) {
             final AppOpsService.UidState uidState = uidStates.valueAt(i);
-            if (uidState.opModes != null) {
-                final int uidMode1 = uidState.opModes.get(op1, defaultModeOp1);
-                final int uidMode2 = uidState.opModes.get(op2, defaultModeOp2);
-                assertEquals(uidMode1, uidMode2);
-                if (uidMode1 != defaultModeOp1) {
-                    numberOfNonDefaultOps++;
-                }
+            final int uidMode1 = uidState.hasOpMode(op1) ? uidState.getOpMode(op1) : defaultModeOp1;
+            final int uidMode2 = uidState.hasOpMode(op2) ? uidState.getOpMode(op2) : defaultModeOp2;
+            assertEquals(uidMode1, uidMode2);
+            if (uidMode1 != defaultModeOp1) {
+                numberOfNonDefaultOps++;
             }
             if (uidState.pkgOps == null) {
                 continue;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 5c2ad94..29a8dad 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -126,6 +126,22 @@
         doTestConnectionDisconnectionReconnection(AudioService.BECOMING_NOISY_DELAY_MS / 2);
     }
 
+    /**
+     * Verify connecting an A2DP sink will call into AudioService to unmute media
+     */
+    @Test
+    public void testA2dpConnectionUnmutesMedia() throws Exception {
+        Log.i(TAG, "testA2dpConnectionUnmutesMedia");
+        Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
+
+        mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
+                BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+        verify(mMockAudioService, times(1)).postAccessoryPlugMediaUnmute(
+                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
+
+    }
+
     private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection)
             throws Exception {
         when(mMockAudioService.getDeviceForStream(AudioManager.STREAM_MUSIC))
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/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 67d6eda..7354ad4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -92,24 +92,18 @@
 
     public void testChangePasswordFailPrimaryUser() throws RemoteException {
         final long sid = 1234;
-        final String FAILED_MESSAGE = "Failed to enroll password";
         initializeStorageWithCredential(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
 
-        try {
-            mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
-                    "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
-            fail("Did not fail when enrolling using incorrect credential");
-        } catch (IllegalStateException expected) {
-            assertTrue(expected.getMessage().equals(FAILED_MESSAGE));
-        }
+        assertFalse(mService.setLockCredential("newpwd".getBytes(), CREDENTIAL_TYPE_PASSWORD,
+                    "badpwd".getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         assertVerifyCredentials(PRIMARY_USER_ID, "password", CREDENTIAL_TYPE_PASSWORD, sid);
     }
 
     public void testClearPasswordPrimaryUser() throws RemoteException {
         final String PASSWORD = "password";
         initializeStorageWithCredential(PRIMARY_USER_ID, PASSWORD, CREDENTIAL_TYPE_PASSWORD, 1234);
-        mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
-                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false);
+        assertTrue(mService.setLockCredential(null, CREDENTIAL_TYPE_NONE, PASSWORD.getBytes(),
+                PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID, false));
         assertFalse(mService.havePassword(PRIMARY_USER_ID));
         assertFalse(mService.havePattern(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -118,9 +112,9 @@
     public void testManagedProfileUnifiedChallenge() throws RemoteException {
         final String firstUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-1";
         final String secondUnifiedPassword = "testManagedProfileUnifiedChallenge-pwd-2";
-        mService.setLockCredential(firstUnifiedPassword.getBytes(),
+        assertTrue(mService.setLockCredential(firstUnifiedPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false);
+                null, PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
         final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
@@ -154,17 +148,17 @@
          */
         mStorageManager.setIgnoreBadUnlock(true);
         // Change primary password and verify that profile SID remains
-        mService.setLockCredential(secondUnifiedPassword.getBytes(),
+        assertTrue(mService.setLockCredential(secondUnifiedPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, firstUnifiedPassword.getBytes(),
-                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+                PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertNull(mGateKeeperService.getAuthToken(TURNED_OFF_PROFILE_USER_ID));
 
         // Clear unified challenge
-        mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
+        assertTrue(mService.setLockCredential(null, LockPatternUtils.CREDENTIAL_TYPE_NONE,
                 secondUnifiedPassword.getBytes(), PASSWORD_QUALITY_UNSPECIFIED, PRIMARY_USER_ID,
-                false);
+                false));
         assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
         assertEquals(0, mGateKeeperService.getSecureUserId(TURNED_OFF_PROFILE_USER_ID));
@@ -173,17 +167,17 @@
     public void testManagedProfileSeparateChallenge() throws RemoteException {
         final String primaryPassword = "testManagedProfileSeparateChallenge-primary";
         final String profilePassword = "testManagedProfileSeparateChallenge-profile";
-        mService.setLockCredential(primaryPassword.getBytes(),
+        assertTrue(mService.setLockCredential(primaryPassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false);
+                PASSWORD_QUALITY_COMPLEX, PRIMARY_USER_ID, false));
         /* Currently in LockSettingsService.setLockCredential, unlockUser() is called with the new
          * credential as part of verifyCredential() before the new credential is committed in
          * StorageManager. So we relax the check in our mock StorageManager to allow that.
          */
         mStorageManager.setIgnoreBadUnlock(true);
-        mService.setLockCredential(profilePassword.getBytes(),
+        assertTrue(mService.setLockCredential(profilePassword.getBytes(),
                 LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
-                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false);
+                PASSWORD_QUALITY_COMPLEX, MANAGED_PROFILE_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
 
         final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
@@ -209,8 +203,9 @@
 
         // Change primary credential and make sure we don't affect profile
         mStorageManager.setIgnoreBadUnlock(true);
-        mService.setLockCredential("pwd".getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
-                primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
+        assertTrue(mService.setLockCredential("pwd".getBytes(),
+                LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                primaryPassword.getBytes(), PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false));
         mStorageManager.setIgnoreBadUnlock(false);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 profilePassword.getBytes(), LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0,
@@ -221,13 +216,13 @@
     public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
         final byte[] password = "password".getBytes();
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 password,
                 CREDENTIAL_TYPE_PASSWORD,
                 null,
                 PASSWORD_QUALITY_ALPHABETIC,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
@@ -237,13 +232,13 @@
             throws Exception {
         final byte[] pattern = "12345".getBytes();
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 pattern,
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
@@ -259,13 +254,13 @@
                 CREDENTIAL_TYPE_PATTERN,
                 PASSWORD_QUALITY_SOMETHING);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 newCredential,
                 CREDENTIAL_TYPE_PASSWORD,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_ALPHABETIC,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(
@@ -276,13 +271,13 @@
             throws Exception {
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 "12345".getBytes(),
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager, never())
                 .lockScreenSecretChanged(
@@ -298,13 +293,13 @@
                 PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 newCredential,
                 CREDENTIAL_TYPE_PASSWORD,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_ALPHABETIC,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID);
@@ -321,13 +316,13 @@
                 PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 null,
                 CREDENTIAL_TYPE_NONE,
                 oldCredential.getBytes(),
                 PASSWORD_QUALITY_UNSPECIFIED,
                 PRIMARY_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID);
@@ -343,13 +338,13 @@
                 PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
         mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
 
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 profilePassword,
                 CREDENTIAL_TYPE_PASSWORD,
                 null,
                 PASSWORD_QUALITY_ALPHABETIC,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
 
         verify(mRecoverableKeyStoreManager)
                 .lockScreenSecretChanged(
@@ -395,13 +390,13 @@
     public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
             throws Exception {
         final byte[] pattern = "12345".getBytes();
-        mService.setLockCredential(
+        assertTrue(mService.setLockCredential(
                 pattern,
                 CREDENTIAL_TYPE_PATTERN,
                 null,
                 PASSWORD_QUALITY_SOMETHING,
                 MANAGED_PROFILE_USER_ID,
-                false);
+                false));
         reset(mRecoverableKeyStoreManager);
 
         mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID);
@@ -436,8 +431,8 @@
 
     private void testCreateCredential(int userId, String credential, int type, int quality)
             throws RemoteException {
-        mService.setLockCredential(credential.getBytes(), type, null, quality,
-                userId, false);
+        assertTrue(mService.setLockCredential(credential.getBytes(), type, null, quality,
+                userId, false));
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
@@ -461,8 +456,8 @@
             String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
         initializeStorageWithCredential(userId, oldCredential, oldType, sid);
-        mService.setLockCredential(newCredential.getBytes(), newType, oldCredential.getBytes(),
-                quality, userId, false);
+        assertTrue(mService.setLockCredential(newCredential.getBytes(), newType,
+                oldCredential.getBytes(), quality, userId, false));
         assertVerifyCredentials(userId, newCredential, newType, sid);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 341b8f0..127cf49 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -341,7 +341,10 @@
         final byte[] pattern = "123654".getBytes();
         final byte[] token = "some-high-entropy-secure-token".getBytes();
         initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+        // Disregard any reportPasswordChanged() invocations as part of credential setup.
+        flushHandlerTasks();
         reset(mDevicePolicyManager);
+
         final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
 
         assertFalse(mService.hasPendingEscrowToken(PRIMARY_USER_ID));
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/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index 8cb5197..0b8c2a55 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -45,8 +45,6 @@
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Set;
 
 @RunWith(JUnit4.class)
 public class AppDataRollbackHelperTest {
@@ -250,28 +248,22 @@
         dataForRestore.info.getPackages().add(pendingRestore);
         dataForRestore.info.getPackages().add(wasRecentlyRestored);
 
-        Set<Rollback> changed = helper.commitPendingBackupAndRestoreForUser(37,
-                Arrays.asList(dataWithPendingBackup, dataWithRecentRestore, dataForDifferentUser,
-                    dataForRestore));
         InOrder inOrder = Mockito.inOrder(installer);
 
         // Check that pending backup and restore for the same package mutually destroyed each other.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithRecentRestore));
         assertEquals(-1, wasRecentlyRestored.getPendingBackups().indexOf(37));
         assertNull(wasRecentlyRestored.getRestoreInfo(37));
 
         // Check that backup was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataWithPendingBackup));
         inOrder.verify(installer).snapshotAppData(eq("com.foo"), eq(37), eq(101),
                 eq(Installer.FLAG_STORAGE_CE));
         assertEquals(-1, pendingBackup.getPendingBackups().indexOf(37));
         assertEquals(53, pendingBackup.getCeSnapshotInodes().get(37));
 
-        // Check that changed returns correct Rollback.
-        assertEquals(3, changed.size());
-        assertTrue(changed.contains(dataWithPendingBackup));
-        assertTrue(changed.contains(dataWithRecentRestore));
-        assertTrue(changed.contains(dataForRestore));
-
         // Check that restore was performed.
+        assertTrue(helper.commitPendingBackupAndRestoreForUser(37, dataForRestore));
         inOrder.verify(installer).restoreAppDataSnapshot(
                 eq("com.abc"), eq(57) /* appId */, eq("seInfo"), eq(37) /* userId */,
                 eq(17239) /* rollbackId */, eq(Installer.FLAG_STORAGE_CE));
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
new file mode 100644
index 0000000..b5925a6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.rollback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.util.IntArray;
+import android.util.SparseLongArray;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@RunWith(JUnit4.class)
+public class RollbackUnitTest {
+
+    @Test
+    public void newEmptyStagedRollbackDefaults() {
+        int rollbackId = 123;
+        int sessionId = 567;
+        File file = new File("/test/testing");
+
+        Rollback rollback = new Rollback(rollbackId, file, sessionId);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+        assertThat(rollback.isStaged()).isTrue();
+        assertThat(rollback.getStagedSessionId()).isEqualTo(567);
+    }
+
+    @Test
+    public void newEmptyNonStagedRollbackDefaults() {
+        int rollbackId = 123;
+        File file = new File("/test/testing");
+
+        Rollback rollback = new Rollback(rollbackId, file, -1);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.getBackupDir().getAbsolutePath()).isEqualTo("/test/testing");
+        assertThat(rollback.isStaged()).isFalse();
+    }
+
+    @Test
+    public void rollbackStateChanges() {
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+
+        assertThat(rollback.isEnabling()).isTrue();
+        assertThat(rollback.isAvailable()).isFalse();
+        assertThat(rollback.isCommitted()).isFalse();
+
+        rollback.setAvailable();
+
+        assertThat(rollback.isEnabling()).isFalse();
+        assertThat(rollback.isAvailable()).isTrue();
+        assertThat(rollback.isCommitted()).isFalse();
+
+        rollback.setCommitted();
+
+        assertThat(rollback.isEnabling()).isFalse();
+        assertThat(rollback.isAvailable()).isFalse();
+        assertThat(rollback.isCommitted()).isTrue();
+    }
+
+    @Test
+    public void getPackageNamesAllAndJustApex() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 12, 10, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 12, 10, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 12, 10, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.getPackageNames()).containsExactly(pkg1, pkg2, pkg3, pkg4);
+        assertThat(rollback.getApexPackageNames()).containsExactly(pkg2, pkg4);
+    }
+
+    @Test
+    public void includesPackages() {
+        String pkg1 = "test.testpackage.pkg1";
+        String pkg2 = "test.testpackage.pkg2";
+        String pkg3 = "com.blah.hello.three";
+        String pkg4 = "com.something.4pack";
+
+        Rollback rollback = new Rollback(123, new File("/test/testing"), -1);
+        PackageRollbackInfo pkgInfo1 = pkgInfoFor(pkg1, 12, 10, false);
+        PackageRollbackInfo pkgInfo2 = pkgInfoFor(pkg2, 18, 12, true);
+        PackageRollbackInfo pkgInfo3 = pkgInfoFor(pkg3, 157, 156, false);
+        PackageRollbackInfo pkgInfo4 = pkgInfoFor(pkg4, 99, 1, true);
+
+        rollback.info.getPackages().addAll(Arrays.asList(pkgInfo1, pkgInfo2, pkgInfo3, pkgInfo4));
+
+        assertThat(rollback.includesPackage(pkg2)).isTrue();
+        assertThat(rollback.includesPackage(pkg3)).isTrue();
+        assertThat(rollback.includesPackage("com.something.else")).isFalse();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 12)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg1, 1)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 18)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg2, 12)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 157)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 156)).isTrue();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg3, 15)).isTrue();
+
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 99)).isFalse();
+        assertThat(rollback.includesPackageWithDifferentVersion(pkg4, 100)).isTrue();
+    }
+
+    private static PackageRollbackInfo pkgInfoFor(
+            String packageName, long fromVersion, long toVersion, boolean isApex) {
+        return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
+                new VersionedPackage(packageName, toVersion),
+                new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
new file mode 100644
index 0000000..8cbf8e5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/IonMemoryUtilTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import static com.android.server.stats.IonMemoryUtil.parseIonHeapSizeFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.parseProcessIonHeapSizesFromDebugfs;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:IonMemoryUtilTest
+ */
+@SmallTest
+public class IonMemoryUtilTest {
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join(
+            "\n",
+            "          client              pid             size",
+            "----------------------------------------------------",
+            " audio@2.0-servi              765             4096",
+            " audio@2.0-servi              765            61440",
+            " audio@2.0-servi              765             4096",
+            "     voip_client               96             8192",
+            "     voip_client               96             4096",
+            "   system_server             1232         16728064",
+            "  surfaceflinger              611         50642944",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total          55193600",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    // Repeated lines have been removed.
+    private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join(
+            "\n",
+            "          client              pid             size      page counts"
+                    + "--------------------------------------------------       4K       8K      "
+                    + "16K      32K      64K     128K     256K     512K       1M       2M       "
+                    + "4M      >=8M",
+            "   system_server             1705         58097664    13120      532        "
+                    + "0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851            16384        0        2        0        "
+                    + "0        0        0        0        0        0        0        "
+                    + "0        0M",
+            " audio@2.0-servi              851             4096        1        0        0       "
+                    + " 0        0        0        0        0        0        0        0        "
+                    + "0M",
+            " audio@2.0-servi              851             4096        1        0      "
+                    + "  0        0        0        0        0        0        0        0        "
+                    + "0        0M",
+            "----------------------------------------------------",
+            "orphaned allocations (info is from last known client):",
+            "----------------------------------------------------",
+            "  total orphaned                0",
+            "          total         159928320",
+            "   deferred free                0",
+            "----------------------------------------------------",
+            "0 order 4 highmem pages in uncached pool = 0 total",
+            "0 order 4 lowmem pages in uncached pool = 0 total",
+            "1251 order 4 lowmem pages in cached pool = 81985536 total",
+            "VMID 8: 0 order 4 highmem pages in secure pool = 0 total",
+            "VMID  8: 0 order 4 lowmem pages in secure pool = 0 total",
+            "--------------------------------------------",
+            "uncached pool = 4096 cached pool = 83566592 secure pool = 0",
+            "pool total (uncached + cached + secure) = 83570688",
+            "--------------------------------------------");
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_emptyContents() {
+        assertThat(parseIonHeapSizeFromDebugfs("")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_invalidValue() {
+        assertThat(parseIonHeapSizeFromDebugfs("<<no-value>>")).isEqualTo(0);
+
+        assertThat(parseIonHeapSizeFromDebugfs("\ntotal 12345678901234567890\n")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseIonHeapSizeFromDebugfs_correctValue() {
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .isEqualTo(55193600);
+
+        assertThat(parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .isEqualTo(159928320);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("")).hasSize(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs("<<no-value>>").size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS))
+                .containsExactly(
+                        createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440),
+                        createIonAllocations(96, 8192 + 4096, 2, 8192),
+                        createIonAllocations(1232, 16728064, 1, 16728064),
+                        createIonAllocations(611, 50642944, 1, 50642944));
+    }
+
+    @Test
+    public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() {
+        assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO))
+                .containsExactly(
+                        createIonAllocations(1705, 58097664, 1, 58097664),
+                        createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384));
+    }
+
+    private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count,
+            long maxSizeInBytes) {
+        IonAllocations allocations = new IonAllocations();
+        allocations.pid = pid;
+        allocations.totalSizeInBytes = totalSizeInBytes;
+        allocations.count = count;
+        allocations.maxSizeInBytes = maxSizeInBytes;
+        return allocations;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
new file mode 100644
index 0000000..ae57774
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import static com.android.server.stats.ProcfsMemoryUtil.parseMemorySnapshotFromStatus;
+import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ProcfsMemoryUtilTest
+ */
+@SmallTest
+public class ProcfsMemoryUtilTest {
+    private static final String STATUS_CONTENTS = "Name:\tandroid.youtube\n"
+            + "State:\tS (sleeping)\n"
+            + "Tgid:\t12088\n"
+            + "Pid:\t12088\n"
+            + "PPid:\t723\n"
+            + "TracerPid:\t0\n"
+            + "Uid:\t10083\t10083\t10083\t10083\n"
+            + "Gid:\t10083\t10083\t10083\t10083\n"
+            + "Ngid:\t0\n"
+            + "FDSize:\t128\n"
+            + "Groups:\t3003 9997 20083 50083 \n"
+            + "VmPeak:\t 4546844 kB\n"
+            + "VmSize:\t 4542636 kB\n"
+            + "VmLck:\t       0 kB\n"
+            + "VmPin:\t       0 kB\n"
+            + "VmHWM:\t  137668 kB\n" // RSS high-water mark
+            + "VmRSS:\t  126776 kB\n" // RSS
+            + "RssAnon:\t   37860 kB\n"
+            + "RssFile:\t   88764 kB\n"
+            + "RssShmem:\t     152 kB\n"
+            + "VmData:\t 4125112 kB\n"
+            + "VmStk:\t    8192 kB\n"
+            + "VmExe:\t      24 kB\n"
+            + "VmLib:\t  102432 kB\n"
+            + "VmPTE:\t    1300 kB\n"
+            + "VmPMD:\t      36 kB\n"
+            + "VmSwap:\t      22 kB\n" // Swap
+            + "Threads:\t95\n"
+            + "SigQ:\t0/13641\n"
+            + "SigPnd:\t0000000000000000\n"
+            + "ShdPnd:\t0000000000000000\n"
+            + "SigBlk:\t0000000000001204\n"
+            + "SigIgn:\t0000000000000001\n"
+            + "SigCgt:\t00000006400084f8\n"
+            + "CapInh:\t0000000000000000\n"
+            + "CapPrm:\t0000000000000000\n"
+            + "CapEff:\t0000000000000000\n"
+            + "CapBnd:\t0000000000000000\n"
+            + "CapAmb:\t0000000000000000\n"
+            + "Seccomp:\t2\n"
+            + "Cpus_allowed:\tff\n"
+            + "Cpus_allowed_list:\t0-7\n"
+            + "Mems_allowed:\t1\n"
+            + "Mems_allowed_list:\t0\n"
+            + "voluntary_ctxt_switches:\t903\n"
+            + "nonvoluntary_ctxt_switches:\t104\n";
+
+    @Test
+    public void testParseVmHWMFromStatus_parsesCorrectValue() {
+        assertThat(parseVmHWMFromStatus(STATUS_CONTENTS)).isEqualTo(137668);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_invalidValue() {
+        assertThat(parseVmHWMFromStatus("test\nVmHWM: x0x0x\ntest")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseVmHWMFromStatus_emptyContents() {
+        assertThat(parseVmHWMFromStatus("")).isEqualTo(0);
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_parsesCorrectValue() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus(STATUS_CONTENTS);
+        assertThat(snapshot.rssInKilobytes).isEqualTo(126776);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(37860);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(22);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_invalidValue() {
+        MemorySnapshot snapshot =
+                parseMemorySnapshotFromStatus("test\nVmRSS:\tx0x0x\nVmSwap:\t1 kB\ntest");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(1);
+        assertThat(snapshot.isEmpty()).isFalse();
+    }
+
+    @Test
+    public void testParseMemorySnapshotFromStatus_emptyContents() {
+        MemorySnapshot snapshot = parseMemorySnapshotFromStatus("");
+        assertThat(snapshot.rssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.anonRssInKilobytes).isEqualTo(0);
+        assertThat(snapshot.swapInKilobytes).isEqualTo(0);
+        assertThat(snapshot.isEmpty()).isTrue();
+    }
+}
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index 7453c48..180deb5 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -31,6 +31,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/>
+    <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 14b71ec..3c2d550 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -27,9 +27,11 @@
 
 import com.android.server.uri.UriGrantsManagerInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 public class UiServiceTestCase {
@@ -77,4 +79,9 @@
         when(mUgmInternal.checkGrantUriPermission(
                 anyInt(), anyString(), any(Uri.class), anyInt(), anyInt())).thenReturn(-1);
     }
+
+    @After
+    public final void cleanUpMockito() {
+        Mockito.framework().clearInlineMocks();
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 273a9e6..7459c4b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -86,7 +86,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -100,7 +100,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
@@ -114,7 +114,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_UNSPECIFIED);
 
@@ -128,7 +128,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(true);
+        when(mConfig.bubblesEnabled()).thenReturn(true);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(false);
         NotificationRecord r = getNotificationRecord(false, IMPORTANCE_UNSPECIFIED);
 
@@ -142,7 +142,7 @@
         BubbleExtractor extractor = new BubbleExtractor();
         extractor.setConfig(mConfig);
 
-        when(mConfig.bubblesEnabled(mUser)).thenReturn(false);
+        when(mConfig.bubblesEnabled()).thenReturn(false);
         when(mConfig.areBubblesAllowed(mPkg, mUid)).thenReturn(true);
         NotificationRecord r = getNotificationRecord(true, IMPORTANCE_HIGH);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 8aaf29a..7b7470c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -58,13 +57,13 @@
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.server.UiServiceTestCase;
 
 import com.google.android.collect.Lists;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -76,6 +75,7 @@
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -331,6 +331,147 @@
         }
     }
 
+    /** Test that restore ignores the user id attribute and applies the data to the target user. */
+    @Test
+    public void testWriteReadXml_writeReadDefaults() throws Exception {
+        // setup
+        ManagedServices service1 =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        ManagedServices service2 =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        XmlSerializer serializer = new FastXmlSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        BufferedOutputStream outStream = new BufferedOutputStream(baos);
+        serializer.setOutput(outStream, "utf-8");
+
+        //data setup
+        service1.addDefaultComponentOrPackage("package/class");
+        serializer.startDocument(null, true);
+        service1.writeXml(serializer, false, 0);
+        serializer.endDocument();
+        outStream.flush();
+
+        final XmlPullParser parser = Xml.newPullParser();
+        BufferedInputStream input = new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray()));
+
+        parser.setInput(input, StandardCharsets.UTF_8.name());
+        XmlUtils.beginDocument(parser, "test");
+        service2.readXml(parser, null, false, 0);
+        ArraySet<ComponentName> defaults = service2.getDefaultComponents();
+
+        assertEquals(1, defaults.size());
+        assertEquals(new ComponentName("package", "class"), defaults.valueAt(0));
+
+    }
+
+    @Test
+    public void resetPackage_enableDefaultsOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> componentsToActivate =
+                service.resetComponents("package", 0);
+
+        assertEquals(1, componentsToActivate.get(true).size());
+        assertEquals(new ComponentName("package", "default"),
+                componentsToActivate.get(true).get(0));
+    }
+
+
+    @Test
+    public void resetPackage_nonDefaultsRemoved() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        ArrayMap<Boolean, ArrayList<ComponentName>> componentsToActivate =
+                service.resetComponents("package", 0);
+
+        assertEquals(1, componentsToActivate.get(true).size());
+        assertEquals(new ComponentName("package", "not-default"),
+                componentsToActivate.get(false).get(0));
+    }
+
+    @Test
+    public void resetPackage_onlyDefaultsOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        assertEquals(3, service.getAllowedComponents(0).size());
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(0);
+        assertEquals(3, components.size());
+        assertTrue(components.contains(new ComponentName("package", "default")));
+    }
+
+    @Test
+    public void resetPackage_affectCurrentUserOnly() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                1, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(1);
+        assertEquals(3, components.size());
+    }
+
+    @Test
+    public void resetPackage_samePackageMultipleClasses() {
+        // setup
+        ManagedServices service =
+                new TestManagedServices(
+                        getContext(), mLock, mUserProfiles, mIpm, APPROVAL_BY_COMPONENT);
+        service.addApprovedList(
+                "package/not-default:another-package/not-default:package2/default",
+                0, true);
+        service.addApprovedList(
+                "package/class:another-package/class:package2/class",
+                0, true);
+        service.addDefaultComponentOrPackage("package/default");
+        service.addDefaultComponentOrPackage("package2/default");
+
+        service.resetComponents("package", 0);
+
+        List<ComponentName> components =  service.getAllowedComponents(0);
+        assertEquals(5, components.size());
+        assertTrue(components.contains(new ComponentName("package", "default")));
+    }
+
     /** Test that backup only writes packages/components that belong to the target user. */
     @Test
     public void testWriteXml_onlyBackupsForTargetUser() throws Exception {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index e15af3d..0b4760d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -68,6 +68,7 @@
     private final int uid2 = 1111111;
     private static final String TEST_CHANNEL_ID = "test_channel_id";
 
+    private NotificationRecord mRecordMinCallNonInterruptive;
     private NotificationRecord mRecordMinCall;
     private NotificationRecord mRecordHighCall;
     private NotificationRecord mRecordDefaultMedia;
@@ -105,6 +106,18 @@
         smsPkg = Settings.Secure.getString(mContext.getContentResolver(),
                 Settings.Secure.SMS_DEFAULT_APPLICATION);
 
+        Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+                .setCategory(Notification.CATEGORY_CALL)
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        mRecordMinCallNonInterruptive = new NotificationRecord(mContext,
+                new StatusBarNotification(callPkg,
+                        callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid,
+                        nonInterruptiveNotif,
+                        new UserHandle(userId), "", 2000), getDefaultChannel());
+        mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+        mRecordMinCallNonInterruptive.setInterruptive(false);
+
         Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setCategory(Notification.CATEGORY_CALL)
                 .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
@@ -113,6 +126,7 @@
                 callPkg, 1, "minCall", callUid, callUid, n1,
                 new UserHandle(userId), "", 2000), getDefaultChannel());
         mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN);
+        mRecordMinCall.setInterruptive(true);
 
         Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
                 .setCategory(Notification.CATEGORY_CALL)
@@ -245,6 +259,7 @@
         expected.add(mRecordCheater);
         expected.add(mRecordCheaterColorized);
         expected.add(mRecordMinCall);
+        expected.add(mRecordMinCallNonInterruptive);
 
         List<NotificationRecord> actual = new ArrayList<>();
         actual.addAll(expected);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 397d215..a9fe1a6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -51,6 +51,8 @@
 import android.service.notification.SnoozeCriterion;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.server.UiServiceTestCase;
 
 import org.junit.After;
@@ -61,8 +63,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.test.runner.AndroidJUnit4;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class NotificationListenerServiceTest extends UiServiceTestCase {
@@ -116,6 +116,7 @@
             assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
             assertEquals(getSmartReplies(key, i), ranking.getSmartReplies());
             assertEquals(canBubble(i), ranking.canBubble());
+            assertEquals(visuallyInterruptive(i), ranking.visuallyInterruptive());
         }
     }
 
@@ -182,7 +183,8 @@
                 tweak.isNoisy(),
                 (ArrayList) tweak.getSmartActions(),
                 (ArrayList) tweak.getSmartReplies(),
-                tweak.canBubble()
+                tweak.canBubble(),
+                tweak.visuallyInterruptive()
         );
         assertNotEquals(nru, nru2);
     }
@@ -258,7 +260,8 @@
                     getNoisy(i),
                     getSmartActions(key, i),
                     getSmartReplies(key, i),
-                    canBubble(i)
+                    canBubble(i),
+                    visuallyInterruptive(i)
             );
             rankings[i] = ranking;
         }
@@ -363,6 +366,10 @@
         return index % 4 == 0;
     }
 
+    private boolean visuallyInterruptive(int index) {
+        return index % 4 == 0;
+    }
+
     private void assertActionsEqual(
             List<Notification.Action> expecteds, List<Notification.Action> actuals) {
         assertEquals(expecteds.size(), actuals.size());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3ac7a79..2de8d05 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.app.Notification.CATEGORY_CALL;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
@@ -61,6 +62,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
@@ -208,9 +210,10 @@
     private AudioManager mAudioManager;
     @Mock
     ActivityManager mActivityManager;
-    NotificationManagerService.WorkerHandler mHandler;
     @Mock
     Resources mResources;
+    @Mock
+    RankingHandler mRankingHandler;
 
     private NotificationChannel mTestNotificationChannel = new NotificationChannel(
             TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
@@ -341,7 +344,6 @@
 
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
-        mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
         mContext.setMockPackageManager(mPackageManagerClient);
 
@@ -377,6 +379,15 @@
         // Setup managed services
         mListener = mListeners.new ManagedServiceInfo(
                 null, new ComponentName(PKG, "test_class"), mUid, true, null, 0);
+        ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
+        ArraySet<ComponentName> components = new ArraySet<>();
+        components.add(defaultComponent);
+        when(mListeners.getDefaultComponents()).thenReturn(components);
+        when(mConditionProviders.getDefaultPackages())
+                .thenReturn(new ArraySet<>(Arrays.asList("config")));
+        when(mAssistants.getDefaultComponents()).thenReturn(components);
+        when(mAssistants.queryPackageForServices(
+                anyString(), anyInt(), anyInt())).thenReturn(components);
         when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
         ManagedServices.Config listenerConfig = new ManagedServices.Config();
         listenerConfig.xmlTag = NotificationListeners.TAG_ENABLED_NOTIFICATION_LISTENERS;
@@ -390,7 +401,7 @@
 
         when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
 
-        mService.init(mTestableLooper.getLooper(),
+        mService.init(mTestableLooper.getLooper(), mRankingHandler,
                 mPackageManager, mPackageManagerClient, mockLightsManager,
                 mListeners, mAssistants, mConditionProviders,
                 mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
@@ -409,6 +420,7 @@
                 PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
         assertNotNull(mBinderService.getNotificationChannel(
                 PKG, mContext.getUserId(), PKG, TEST_CHANNEL_ID));
+        clearInvocations(mRankingHandler);
     }
 
     @After
@@ -419,6 +431,12 @@
                 .getUiAutomation().dropShellPermissionIdentity();
     }
 
+    private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
+        changed.put(true, new ArrayList<>());
+        changed.put(false, new ArrayList<>());
+        return changed;
+    }
     private ApplicationInfo getApplicationInfo(String pkg, int uid) {
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = uid;
@@ -446,7 +464,7 @@
     private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
             boolean channelEnabled) {
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.bubblesEnabled(any())).thenReturn(globalEnabled);
+        when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
@@ -460,20 +478,31 @@
         Notification.Builder nb = new Notification.Builder(mContext, "a")
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid, "tag", uid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, uid,
+                "tag" + System.currentTimeMillis(), uid, 0,
                 nb.build(), new UserHandle(userId), null, postTime);
         return sbn;
     }
 
+
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
+        return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+            String groupKey, boolean isSummary, boolean isBubble) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
+        if (isBubble) {
+            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
+        }
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
+                "tag" + System.currentTimeMillis(), mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -501,7 +530,7 @@
         if (isBubble) {
             nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
         }
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
@@ -568,6 +597,52 @@
                 .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
     }
 
+    private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
+            throws RemoteException {
+
+        // Notification that has bubble metadata
+        NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
+                "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+
+        // Make the package foreground so that we're allowed to be a bubble
+        when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
+                nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
+        waitForIdle();
+
+        // Make sure we are a bubble
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+        assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
+
+        // Plain notification without bubble metadata
+        NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
+                "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
+                nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(2, notifsAfter.length);
+
+        // Summary notification for both of those
+        NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
+                "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+        if (summaryAutoCancel) {
+            nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
+        }
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nrSummary.sbn.getTag(),
+                nrSummary.sbn.getId(), nrSummary.sbn.getNotification(), nrSummary.sbn.getUserId());
+        waitForIdle();
+
+        notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(3, notifsAfter.length);
+
+        return nrSummary;
+    }
+
     @Test
     public void testCreateNotificationChannels_SingleChannel() throws Exception {
         final NotificationChannel channel =
@@ -702,7 +777,8 @@
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(channel)));
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testBlockedNotifications_blockedChannel",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -720,7 +796,7 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(1, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -749,7 +825,7 @@
 
         StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The first time a foreground service notification is shown, we allow the channel
@@ -771,7 +847,8 @@
 
         sbn = generateNotificationRecord(channel).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_userBlockedChannelForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         // The second time it is shown, we keep the user's preference.
@@ -785,7 +862,8 @@
     public void testBlockedNotifications_blockedChannelGroup() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
         mService.setPreferencesHelper(mPreferencesHelper);
-        when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+        when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).
+                thenReturn(true);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
@@ -802,7 +880,8 @@
         mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_blockedApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -816,7 +895,8 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueuedBlockedNotifications_blockedAppForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
@@ -838,7 +918,8 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testEnqueuedRestrictedNotifications_asSystem",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -862,7 +943,8 @@
             final StatusBarNotification sbn =
                     generateNotificationRecord(mTestNotificationChannel, ++id, "", false).sbn;
             sbn.getNotification().category = category;
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testEnqueuedRestrictedNotifications_notAutomotive",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         waitForIdle();
@@ -885,7 +967,8 @@
             final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
             sbn.getNotification().category = category;
             try {
-                mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                        "testEnqueuedRestrictedNotifications_badUser",
                         sbn.getId(), sbn.getNotification(), sbn.getUserId());
                 fail("Calls from non system apps should not allow use of restricted categories");
             } catch (SecurityException e) {
@@ -922,7 +1005,8 @@
 
     @Test
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testEnqueueNotificationWithTag_PopulatesGetActiveNotifications", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
@@ -932,9 +1016,11 @@
 
     @Test
     public void testCancelNotificationImmediatelyAfterEnqueue() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationImmediatelyAfterEnqueue", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testCancelNotificationImmediatelyAfterEnqueue", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -944,12 +1030,15 @@
 
     @Test
     public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0,
                 generateNotificationRecord(null).getNotification(), 0);
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", 0, 0);
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testCancelNotificationWhilePostedAndEnqueued", 0, 0);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
@@ -964,7 +1053,8 @@
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
         NotificationRecord r = generateNotificationRecord(null);
         final StatusBarNotification sbn = r.sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelNotificationsFromListenerImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
         waitForIdle();
@@ -977,7 +1067,8 @@
     @Test
     public void testCancelAllNotificationsImmediatelyAfterEnqueue() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsImmediatelyAfterEnqueue",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -988,11 +1079,26 @@
     }
 
     @Test
+    public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
+            throws Exception {
+        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        sbn.getNotification().flags =
+                Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        waitForIdle();
+        verify(mListeners, times(1)).notifyPostedLocked(any(), any());
+        verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), any());
+    }
+
+    @Test
     public void testUserInitiatedClearAll_noLeak() throws Exception {
         final NotificationRecord n = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testUserInitiatedClearAll_noLeak",
                 n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
         waitForIdle();
 
@@ -1015,9 +1121,11 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group1", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsCancelsChildren",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotificationsCancelsChildren",
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -1030,7 +1138,8 @@
     public void testCancelAllNotificationsMultipleEnqueuedDoesNotCrash() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testCancelAllNotificationsMultipleEnqueuedDoesNotCrash",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
         }
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
@@ -1049,17 +1158,20 @@
                 mTestNotificationChannel, 2, "group1", false);
 
         // fully post parent notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
         // enqueue the child several times
         for (int i = 0; i < 10; i++) {
-            mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+            mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                    "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                     child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         }
         // make the parent a child, which will cancel the child notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelGroupSummaryMultipleEnqueuedChildrenDoesNotCrash",
                 parentAsChild.sbn.getId(), parentAsChild.sbn.getNotification(),
                 parentAsChild.sbn.getUserId());
         waitForIdle();
@@ -1071,7 +1183,8 @@
     public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreForegroundService",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1085,7 +1198,8 @@
     public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_IgnoreOtherPackages",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId());
         waitForIdle();
@@ -1098,7 +1212,8 @@
     @Test
     public void testCancelAllNotifications_NullPkgRemovesAll() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_NullPkgRemovesAll",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
         waitForIdle();
@@ -1111,7 +1226,8 @@
     @Test
     public void testCancelAllNotifications_NullPkgIgnoresUserAllNotifications() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testCancelAllNotifications_NullPkgIgnoresUserAllNotifications",
                 sbn.getId(), sbn.getNotification(), UserHandle.USER_ALL);
         // Null pkg is how we signal a user switch.
         mBinderService.cancelAllNotifications(null, sbn.getUserId());
@@ -1126,7 +1242,8 @@
     public void testAppInitiatedCancelAllNotifications_CancelsNoClearFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_NO_CLEAR;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppInitiatedCancelAllNotifications_CancelsNoClearFlag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1211,7 +1328,12 @@
 
     @Test
     public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        Notification n =
+                new Notification.Builder(mContext, mTestNotificationChannel.getId())
+                        .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                        .build();
+        StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0,
+                n, new UserHandle(mUid), null, 0);
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
@@ -1228,12 +1350,13 @@
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags =
                 Notification.FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", sbn.getId(), sbn.getUserId());
+        mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
+                sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
         assertEquals(0, mService.getNotificationRecordCount());
@@ -1321,21 +1444,22 @@
         // should not be returned
         final NotificationRecord group2 = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group2", true);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
                 group2.sbn.getId(), group2.sbn.getNotification(), group2.sbn.getUserId());
         waitForIdle();
 
         // should not be returned
         final NotificationRecord nonGroup = generateNotificationRecord(
                 mTestNotificationChannel, 3, null, false);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
                 nonGroup.sbn.getId(), nonGroup.sbn.getNotification(), nonGroup.sbn.getUserId());
         waitForIdle();
 
         // same group, child, should be returned
         final NotificationRecord group1Child = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group1", false);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null, group1Child.sbn.getId(),
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testFindGroupNotificationsLocked",
+                group1Child.sbn.getId(),
                 group1Child.sbn.getNotification(), group1Child.sbn.getUserId());
         waitForIdle();
 
@@ -1392,7 +1516,8 @@
     public void testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag() throws Exception {
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppInitiatedCancelAllNotifications_CancelsOnGoingFlag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
@@ -1509,7 +1634,7 @@
                         new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean());
@@ -1524,8 +1649,8 @@
                 mTestNotificationChannel);
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag", 0,
-                generateNotificationRecord(null, tv).getNotification(), 0);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
+                0, generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
                 anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
     }
@@ -2165,7 +2290,7 @@
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
                 child.sbn.getId(), child.sbn.getNotification(), child.sbn.getUserId());
         waitForIdle();
 
@@ -2178,7 +2303,7 @@
         final NotificationRecord record = generateNotificationRecord(
                 mTestNotificationChannel, 2, null, false);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostNonGroup_noUnsnoozing",
                 record.sbn.getId(), record.sbn.getNotification(), record.sbn.getUserId());
         waitForIdle();
 
@@ -2190,7 +2315,7 @@
         final NotificationRecord parent = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", true);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "testPostGroupSummary_noUnsnoozing",
                 parent.sbn.getId(), parent.sbn.getNotification(), parent.sbn.getUserId());
         waitForIdle();
 
@@ -2604,31 +2729,37 @@
                 .setColorized(true)
                 .setFlag(Notification.FLAG_CAN_COLORIZE, true)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testNoFakeColorizedPermission", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         NotificationRecord posted = mService.findNotificationLocked(
-                PKG, null, nr.sbn.getId(), nr.sbn.getUserId());
+                PKG, nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getUserId());
 
         assertFalse(posted.getNotification().isColorized());
     }
 
     @Test
-    public void testGetNotificationCountLocked() throws Exception {
+    public void testGetNotificationCountLocked() {
+        String sampleTagToExclude = null;
+        int sampleIdToExclude = 0;
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
             mService.addEnqueuedNotification(r);
+
         }
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
             mService.addNotification(r);
+            sampleTagToExclude = r.sbn.getTag();
+            sampleIdToExclude = i;
         }
 
         // another package
@@ -2645,55 +2776,49 @@
         mService.addNotification(otherPackage);
 
         // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
+        // anything that's currently enqueued or posted
         int userId = new UserHandle(mUid).getIdentifier();
         assertEquals(40,
                 mService.getNotificationCountLocked(PKG, userId, 0, null));
         assertEquals(40,
                 mService.getNotificationCountLocked(PKG, userId, 0, "tag2"));
+
+        // return all for package "a" - "banana" tag isn't used
         assertEquals(2,
                 mService.getNotificationCountLocked("a", userId, 0, "banana"));
 
         // exclude a known notification - it's excluded from only the posted list, not enqueued
-        assertEquals(39,
-                mService.getNotificationCountLocked(PKG, userId, 0, "tag"));
+        assertEquals(39, mService.getNotificationCountLocked(
+                PKG, userId, sampleIdToExclude, sampleTagToExclude));
     }
 
     @Test
     public void testAddAutogroup_requestsSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
         mService.addAutogroupKeyLocked(r.getKey());
 
-        verify(rh, times(1)).requestSort();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testRemoveAutogroup_requestsSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
         mService.addNotification(r);
         mService.removeAutogroupKeyLocked(r.getKey());
 
-        verify(rh, times(1)).requestSort();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testReaddAutogroup_noSort() throws Exception {
-        RankingHandler rh = mock(RankingHandler.class);
-        mService.setRankingHandler(rh);
-
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
         mService.addNotification(r);
         mService.addAutogroupKeyLocked(r.getKey());
 
-        verify(rh, never()).requestSort();
+        verify(mRankingHandler, never()).requestSort();
     }
 
     @Test
@@ -2867,7 +2992,8 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag",
+        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+                "testBumpFGImportance_noChannelChangePreOApp",
                 Binder.getCallingUid(), 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
         mBinderService.enqueueNotificationWithTag(sbn.getPackageName(), sbn.getOpPkg(),
@@ -2883,10 +3009,12 @@
                 .setFlag(FLAG_FOREGROUND_SERVICE, true)
                 .setPriority(Notification.PRIORITY_MIN);
 
-        sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", Binder.getCallingUid(),
+        sbn = new StatusBarNotification(preOPkg, preOPkg, 9,
+                "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(),
                 0, nb.build(), new UserHandle(Binder.getCallingUid()), null, 0);
 
-        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
+        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg,
+                "testBumpFGImportance_noChannelChangePreOApp",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
         assertEquals(IMPORTANCE_LOW,
@@ -3099,9 +3227,6 @@
     public void testUserSentimentChangeTriggersUpdate() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3113,16 +3238,13 @@
 
         waitForIdle();
 
-        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
+        verify(mRankingHandler, timeout(300).times(1)).requestSort();
     }
 
     @Test
     public void testTooLateAdjustmentTriggersUpdate() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3134,16 +3256,13 @@
 
         waitForIdle();
 
-        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
+        verify(mRankingHandler, times(1)).requestSort();
     }
 
     @Test
     public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addEnqueuedNotification(r);
-        NotificationManagerService.WorkerHandler handler = mock(
-                NotificationManagerService.WorkerHandler.class);
-        mService.setHandler(handler);
         when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
 
         Bundle signals = new Bundle();
@@ -3153,8 +3272,7 @@
                 r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
         mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment);
 
-        assertEquals(USER_SENTIMENT_NEGATIVE,
-                r.getUserSentiment());
+        assertEquals(USER_SENTIMENT_NEGATIVE, r.getUserSentiment());
     }
 
     @Test
@@ -3186,6 +3304,8 @@
     @Test
     public void testBackup() throws Exception {
         int systemChecks = mService.countSystemChecks;
+        when(mListeners.queryPackageForServices(anyString(), anyInt(), anyInt()))
+                .thenReturn(new ArraySet<>());
         mBinderService.getBackupPayload(1);
         assertEquals(1, mService.countSystemChecks - systemChecks);
     }
@@ -4044,7 +4164,8 @@
 
         final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
         try {
-            mInternalService.enqueueNotification(notReal, "android", 0, 0, "tag",
+            mInternalService.enqueueNotification(notReal, "android", 0, 0,
+                    "testPostFromAndroidForNonExistentPackage",
                     sbn.getId(), sbn.getNotification(), sbn.getUserId());
             fail("can't post notifications for nonexistent packages, even if you exist");
         } catch (SecurityException e) {
@@ -4318,7 +4439,8 @@
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         mService.addNotification(r);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, r.sbn.getId(),
+                r.sbn.getTag(), mUid, 0,
                 new Notification.Builder(mContext, mTestNotificationChannel.getId()).build(),
                 new UserHandle(mUid), null, 0);
         NotificationRecord update = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -4377,16 +4499,20 @@
     public void setDefaultAssistantForUser_fromConfigXml() {
         clearDeviceConfig();
         ComponentName xmlConfig = new ComponentName("config", "xml");
+        ArraySet<ComponentName> components = new ArraySet<>(Arrays.asList(xmlConfig));
         when(mResources
                 .getString(
                         com.android.internal.R.string.config_defaultAssistantAccessComponent))
                 .thenReturn(xmlConfig.flattenToString());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
-                .thenReturn(Collections.singleton(xmlConfig));
+        when(mAssistants.queryPackageForServices(eq(null), anyInt(), anyInt()))
+                .thenReturn(components);
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(components);
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
+
         mService.setDefaultAssistantForUser(0);
 
         verify(mNotificationAssistantAccessGrantedCallback)
@@ -4402,8 +4528,10 @@
                 .getString(com.android.internal.R.string.config_defaultAssistantAccessComponent))
                 .thenReturn(xmlConfig.flattenToString());
         when(mContext.getResources()).thenReturn(mResources);
-        when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
+        when(mAssistants.queryPackageForServices(eq(null), anyInt(), anyInt()))
                 .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig)));
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(new ArraySet<>(Arrays.asList(deviceConfig)));
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
@@ -4424,7 +4552,9 @@
         when(mContext.getResources()).thenReturn(mResources);
         // Only xmlConfig is valid, deviceConfig is not.
         when(mAssistants.queryPackageForServices(eq(null), anyInt(), eq(0)))
-                .thenReturn(Collections.singleton(xmlConfig));
+                .thenReturn(new ArraySet<>(Collections.singleton(xmlConfig)));
+        when(mAssistants.getDefaultComponents())
+                .thenReturn(new ArraySet<>(Arrays.asList(xmlConfig, deviceConfig)));
         mService.setNotificationAssistantAccessGrantedCallback(
                 mNotificationAssistantAccessGrantedCallback);
 
@@ -4435,6 +4565,53 @@
     }
 
     @Test
+    public void clearMultipleDefaultAssistantPackagesShouldEnableOnlyOne() throws RemoteException {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
+                generateResetComponentValues();
+        when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners);
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        ComponentName deviceConfig1 = new ComponentName("device", "config1");
+        ComponentName deviceConfig2 = new ComponentName("device", "config2");
+        changes.put(true, new ArrayList(Arrays.asList(deviceConfig1, deviceConfig2)));
+        changes.put(false, new ArrayList());
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mAssistants, times(1))
+                .setPackageOrComponentEnabled(
+                        eq("device/config2"),
+                        eq(0), eq(true), eq(false));
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                eq("device"), eq(0), eq(false), eq(true));
+    }
+
+    @Test
+    public void clearDefaultListenersPackageShouldEnableIt() throws RemoteException {
+        ArrayMap<Boolean, ArrayList<ComponentName>> changedAssistants =
+                generateResetComponentValues();
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changedAssistants);
+        ComponentName deviceConfig = new ComponentName("device", "config");
+        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
+        changes.put(true, new ArrayList(Arrays.asList(deviceConfig)));
+        changes.put(false, new ArrayList());
+        when(mListeners.resetComponents(anyString(), anyInt()))
+            .thenReturn(changes);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                eq("device"), eq(0), eq(false), eq(true));
+    }
+
+    @Test
+    public void clearDefaultDnDPackageShouldEnableIt() throws RemoteException {
+        ComponentName deviceConfig = new ComponentName("device", "config");
+        ArrayMap<Boolean, ArrayList<ComponentName>> changed = generateResetComponentValues();
+        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changed);
+        when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changed);
+        mService.getBinderService().clearData("device", 0, false);
+        verify(mConditionProviders, times(1)).resetPackage(
+                        eq("device"), eq(0));
+    }
+
+    @Test
     public void testFlagBubble() throws RemoteException {
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
@@ -4447,7 +4624,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4471,7 +4648,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4495,8 +4672,8 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // yes allowed, yes foreground, yes bubble
@@ -4517,7 +4694,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4538,7 +4715,7 @@
         // Send notif when we're foreground
         when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
                 nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4552,7 +4729,7 @@
 
         when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
                 nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
         waitForIdle();
 
@@ -4578,7 +4755,7 @@
         // Send notif when we're foreground
         when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
                 nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4587,7 +4764,7 @@
                 nr1.sbn.getKey()).getNotification().isBubbleNotification());
 
         // Remove the bubble
-        mBinderService.cancelNotificationWithTag(PKG, PKG, "tag", nr1.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(),
                 nr1.sbn.getUserId());
         waitForIdle();
 
@@ -4601,7 +4778,7 @@
 
         when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
                 nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
         waitForIdle();
 
@@ -4647,11 +4824,12 @@
                 .setActions(replyAction)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_flag_messaging", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4680,13 +4858,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_flag_phonecall", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4719,7 +4898,7 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4743,13 +4922,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_noPerson", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4777,13 +4957,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_noCategory", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4817,12 +4998,13 @@
                 )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4840,7 +5022,7 @@
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4874,12 +5056,13 @@
                 )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4908,13 +5091,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_notAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4943,13 +5127,14 @@
                 .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
         NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -4978,7 +5163,8 @@
         nrBubble.sbn.getNotification().flags |= FLAG_BUBBLE;
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG,
+                "testAppCancelNotifications_cancelsBubbles",
                 nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
         waitForIdle();
 
@@ -4986,7 +5172,8 @@
         assertEquals(1, notifs.length);
         assertEquals(1, mService.getNotificationRecordCount());
 
-        mBinderService.cancelNotificationWithTag(PKG, PKG, null, nrBubble.sbn.getId(),
+        mBinderService.cancelNotificationWithTag(PKG, PKG,
+                "testAppCancelNotifications_cancelsBubbles", nrBubble.sbn.getId(),
                 nrBubble.sbn.getUserId());
         waitForIdle();
 
@@ -5141,7 +5328,7 @@
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5171,7 +5358,7 @@
         // Plain notification that has bubble metadata
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5205,7 +5392,7 @@
         // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5235,7 +5422,7 @@
         // Plain notification that has bubble metadata
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
                 null /* tvExtender */, true /* isBubble */);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
@@ -5318,4 +5505,161 @@
 
         verify(mUsageStats, times(5)).registerImageRemoved(PKG);
     }
+
+    public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we're not foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we're not foreground
+        assertFalse(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertFalse(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
+            throws RemoteException {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
+                .setSuppressNotification(true)
+                .setAutoExpandBubble(true).build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        // Ensure we are in the foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        assertTrue(notif.isBubbleNotification());
+
+        // Our flags should have failed since we are foreground
+        assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
+        assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Dismiss summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, nrSummary.sbn.getTag(),
+                nrSummary.sbn.getId(), nrSummary.getUserId(), nrSummary.getKey(),
+                NotificationStats.DISMISSAL_SHADE,
+                NotificationStats.DISMISS_SENTIMENT_NEUTRAL, nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
+
+    @Test
+    public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
+            throws Exception {
+        // Bubbles are allowed!
+        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+
+        NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
+                true /* summaryAutoCancel */);
+
+        // Click summary
+        final NotificationVisibility nv = NotificationVisibility.obtain(nrSummary.getKey(), 1, 2,
+                true);
+        mService.mNotificationDelegate.onNotificationClick(mUid, Binder.getCallingPid(),
+                nrSummary.getKey(), nv);
+        waitForIdle();
+
+        // The bubble should still exist
+        StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifsAfter.length);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 365cd80..80439cf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -60,7 +60,9 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
+
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
@@ -154,8 +156,7 @@
         contentResolver.setFallbackToExisting(false);
         Secure.putIntForUser(contentResolver,
                 Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID_N_MR1));
-        Secure.putIntForUser(contentResolver,
-                Secure.NOTIFICATION_BUBBLES, 1, UserHandle.getUserId(UID_N_MR1));
+        Global.putInt(contentResolver, Global.NOTIFICATION_BUBBLES, 1);
 
         ContentProvider testContentProvider = mock(ContentProvider.class);
         when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
@@ -1950,42 +1951,18 @@
 
     @Test
     public void testBubblesOverrideTrue() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 1);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertTrue(mHelper.bubblesEnabled(USER));
+        assertTrue(mHelper.bubblesEnabled());
     }
 
     @Test
     public void testBubblesOverrideFalse() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
+        Global.putInt(getContext().getContentResolver(),
+                Global.NOTIFICATION_BUBBLES, 0);
         mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-    }
-
-    @Test
-    public void testBubblesForUserAll() {
-        try {
-            mHelper.bubblesEnabled(UserHandle.ALL);
-        } catch (Exception e) {
-            fail("just don't throw");
-        }
-    }
-
-    @Test
-    public void testBubblesOverrideUserIsolation() {
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 0,
-                USER.getIdentifier());
-        Secure.putIntForUser(getContext().getContentResolver(),
-                Secure.NOTIFICATION_BUBBLES, 1,
-                USER2.getIdentifier());
-        mHelper.updateBubblesEnabled(); // would be called by settings observer
-        assertFalse(mHelper.bubblesEnabled(USER));
-        assertTrue(mHelper.bubblesEnabled(USER2));
+        assertFalse(mHelper.bubblesEnabled());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index f37ff11..7f9f489 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -129,7 +129,7 @@
         mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
 
         try {
-            mService.init(mock(Looper.class),
+            mService.init(mock(Looper.class), mock(RankingHandler.class),
                     mock(IPackageManager.class), mock(PackageManager.class),
                     mock(LightsManager.class),
                     mock(NotificationListeners.class), mock(NotificationAssistants.class),
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 977dd8e..aaaa7a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -47,6 +47,7 @@
 import org.mockito.ArgumentMatcher;
 
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests for the {@link ActivityMetricsLaunchObserver} class.
@@ -118,7 +119,7 @@
 
     static <T> T verifyAsync(T mock) {
         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
-        return verify(mock, timeout(100));
+        return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 31babd0..30c8eb3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -100,6 +100,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.concurrent.TimeUnit;
@@ -112,6 +113,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class ActivityRecordTests extends ActivityTestsBase {
     private ActivityStack mStack;
     private TaskRecord mTask;
@@ -644,7 +646,7 @@
         // The override configuration should be reset and the activity's process will be killed.
         assertFalse(mActivity.inSizeCompatMode());
         verify(mActivity).restartProcessIfVisible();
-        mService.mH.runWithScissors(() -> { }, TimeUnit.SECONDS.toMillis(3));
+        mLockRule.runWithScissors(mService.mH, () -> { }, TimeUnit.SECONDS.toMillis(3));
         verify(mService.mAmInternal).killProcess(
                 eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
     }
@@ -656,6 +658,7 @@
 
                     @Override
                     public void onAnimationStart(RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
                             IRemoteAnimationFinishedCallback finishedCallback) {
 
                     }
@@ -798,6 +801,32 @@
     }
 
     /**
+     * Verify that when finishing the top focused activity on top display, the stack order will be
+     * changed by adjusting focus.
+     */
+    @Test
+    public void testFinishActivityIfPossible_adjustStackOrder() {
+        // Prepare the stacks with order (top to bottom): mStack, stack1, stack2.
+        final ActivityStack stack1 = new StackBuilder(mRootActivityContainer).build();
+        mStack.moveToFront("test");
+        // The stack2 is needed here for moving back to simulate the
+        // {@link ActivityDisplay#mPreferredTopFocusableStack} is cleared, so
+        // {@link ActivityDisplay#getFocusedStack} will rely on the order of focusable-and-visible
+        // stacks. Then when mActivity is finishing, its stack will be invisible (no running
+        // activities in the stack) that is the key condition to verify.
+        final ActivityStack stack2 = new StackBuilder(mRootActivityContainer).build();
+        stack2.moveToBack("test", stack2.getChildAt(0));
+
+        assertTrue(mStack.isTopStackOnDisplay());
+
+        mActivity.setState(RESUMED, "test");
+        mActivity.finishIfPossible(0 /* resultCode */, null /* resultData */, "test",
+                false /* oomAdj */);
+
+        assertTrue(stack1.isTopStackOnDisplay());
+    }
+
+    /**
      * Verify that resumed activity is paused due to finish request.
      */
     @Test
@@ -1032,6 +1061,7 @@
         // simulates finishing in non-focused stack in split-screen.
         final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
         stack.getChildAt(0).getChildAt(0).nowVisible = true;
+        stack.getChildAt(0).getChildAt(0).visible = true;
 
         topActivity.completeFinishing("test");
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index c83e5cb..2a8b4c8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -980,6 +981,19 @@
     }
 
     @Test
+    public void testCompletePauseOnResumeWhilePausingActivity() {
+        final ActivityRecord bottomActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        doReturn(true).when(bottomActivity).attachedToProcess();
+        mStack.mPausingActivity = null;
+        mStack.mResumedActivity = bottomActivity;
+        final ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        topActivity.info.flags |= FLAG_RESUME_WHILE_PAUSING;
+
+        mStack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */, topActivity);
+        verify(mStack).completePauseLocked(anyBoolean(), eq(topActivity));
+    }
+
+    @Test
     public void testAdjustFocusedStackToHomeWhenNoActivity() {
         final ActivityStack homeStask = mDefaultDisplay.getHomeStack();
         TaskRecord homeTask = homeStask.topTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 81fbfe4..1f672c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -83,6 +83,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests for the {@link ActivityStarter} class.
@@ -92,6 +93,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class ActivityStarterTests extends ActivityTestsBase {
     private ActivityStarter mStarter;
     private ActivityStartController mController;
@@ -145,8 +147,8 @@
         assertThat((Object) task2.getStack()).isInstanceOf(ActivityStack.class);
         mStarter.updateBounds(task2, bounds);
 
-        verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId),
-                eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt());
+        verify(mService, times(1)).animateResizePinnedStack(eq(task2.getStack().mStackId),
+                eq(bounds), anyInt());
 
         // In the case of no animation, the stack and task bounds should be set immediately.
         if (!ANIMATE) {
@@ -352,7 +354,7 @@
             doReturn(stack).when(mRootActivityContainer)
                     .getLaunchStack(any(), any(), any(), anyBoolean());
             doReturn(stack).when(mRootActivityContainer)
-                    .getLaunchStack(any(), any(), any(), anyBoolean(), any());
+                    .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
         }
 
         // Set up mock package manager internal and make sure no unmocked methods are called
@@ -364,7 +366,7 @@
 
         // Never review permissions
         doReturn(false).when(mockPackageManager).isPermissionsReviewRequired(any(), anyInt());
-        doNothing().when(mockPackageManager).grantEphemeralAccess(
+        doNothing().when(mockPackageManager).grantImplicitAccess(
                 anyInt(), any(), anyInt(), anyInt());
 
         final Intent intent = new Intent();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index a5dc241..d311dfc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -38,7 +38,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.testing.DexmakerShareClassLoaderRule;
@@ -62,6 +61,10 @@
     @Rule
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
 
+    @WindowTestRunner.MethodWrapperRule
+    public final WindowManagerGlobalLockRule mLockRule =
+            new WindowManagerGlobalLockRule(mSystemServicesTestRule);
+
     final Context mContext = getInstrumentation().getTargetContext();
 
     ActivityTaskManagerService mService;
@@ -136,6 +139,8 @@
         private int mScreenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
         private boolean mLaunchTaskBehind;
         private int mConfigChanges;
+        private int mLaunchedFromPid;
+        private int mLaunchedFromUid;
 
         ActivityBuilder(ActivityTaskManagerService service) {
             mService = service;
@@ -211,6 +216,16 @@
             return this;
         }
 
+        ActivityBuilder setLaunchedFromPid(int pid) {
+            mLaunchedFromPid = pid;
+            return this;
+        }
+
+        ActivityBuilder setLaunchedFromUid(int uid) {
+            mLaunchedFromUid = uid;
+            return this;
+        }
+
         ActivityRecord build() {
             if (mComponent == null) {
                 final int id = sCurrentActivityId++;
@@ -247,10 +262,11 @@
             }
 
             final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
-                    0 /* launchedFromPid */, 0, null, intent, null,
-                    aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
-                    0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
-                    mService.mStackSupervisor, options, null /* sourceRecord */);
+                    mLaunchedFromPid /* launchedFromPid */, mLaunchedFromUid /* launchedFromUid */,
+                    null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */,
+                    null /* resultWho */, 0 /* reqCode */, false /*componentSpecified*/,
+                    false /* rootVoiceInteraction */, mService.mStackSupervisor, options,
+                    null /* sourceRecord */);
             spyOn(activity);
             if (mTaskRecord != null) {
                 // fullscreen value is normally read from resources in ctor, so for testing we need
@@ -429,12 +445,7 @@
             final ActivityStackSupervisor supervisor = mRootActivityContainer.mStackSupervisor;
             if (mWindowingMode == WINDOWING_MODE_PINNED) {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
-                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-                };
+                        mWindowingMode, ACTIVITY_TYPE_STANDARD, mOnTop);
             } else {
                 stack = new ActivityStack(mDisplay, stackId, supervisor,
                         mWindowingMode, mActivityType, mOnTop);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 035568f..629a954 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -75,6 +75,7 @@
     class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) {
             for (RemoteAnimationTarget target : apps) {
                 assertNotNull(target.startBounds);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index c162b6a..45e6890 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -248,6 +248,7 @@
         boolean mCancelled = false;
         @Override
         public void onAnimationStart(RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
                 IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
index f6f8811..703ebc9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsTests.java
@@ -51,14 +51,13 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.MockitoSession;
+import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.ByteArrayInputStream;
@@ -78,6 +77,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class DisplayWindowSettingsTests extends WindowTestsBase {
 
     private static final File TEST_FOLDER = getInstrumentation().getTargetContext().getCacheDir();
@@ -444,8 +444,6 @@
         mTarget.setUserRotation(mPrimaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED,
                 Surface.ROTATION_0);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -454,15 +452,12 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_DEFAULT));
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testSetFixedToUserRotationDisabled() {
         mTarget.setFixedToUserRotation(mPrimaryDisplay, FIXED_TO_USER_ROTATION_DISABLED);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -471,15 +466,12 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_DISABLED));
-        mockitoSession.finishMocking();
     }
 
     @Test
     public void testSetFixedToUserRotationEnabled() {
         mTarget.setFixedToUserRotation(mPrimaryDisplay, FIXED_TO_USER_ROTATION_ENABLED);
 
-        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
-                .startMocking();
         final DisplayRotation displayRotation = mock(DisplayRotation.class);
         spyOn(mPrimaryDisplay);
         doReturn(displayRotation).when(mPrimaryDisplay).getDisplayRotation();
@@ -488,7 +480,6 @@
 
         verify(displayRotation).restoreSettings(anyInt(), anyInt(),
                 eq(FIXED_TO_USER_ROTATION_ENABLED));
-        mockitoSession.finishMocking();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index daee911..fa83f85 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -128,9 +128,9 @@
 
         @Override
         public String getProperty(String namespace, String name) {
-            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)
+            if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)
                     || !KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
-                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+                throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
                         + "supported.");
             }
             return mBlacklist;
@@ -140,8 +140,8 @@
         public void addOnPropertiesChangedListener(String namespace, Executor executor,
                 DeviceConfig.OnPropertiesChangedListener listener) {
 
-            if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)) {
-                throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+            if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)) {
+                throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
                         + "supported.");
             }
             mListeners.add(new Pair<>(listener, executor));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
new file mode 100644
index 0000000..f3a8e1a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSourceControl;
+import android.view.test.InsetsModeSession;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class InsetsPolicyTest extends WindowTestsBase {
+    private static InsetsModeSession sInsetsModeSession;
+
+    @BeforeClass
+    public static void setUpOnce() {
+        // To let the insets provider control the insets visibility, the insets mode has to be
+        // NEW_INSETS_MODE_FULL.
+        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        sInsetsModeSession.close();
+    }
+
+    @Test
+    public void testControlsForDispatch_regular() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app can control both system bars.
+        assertNotNull(controls);
+        assertEquals(2, controls.length);
+    }
+
+    @Test
+    public void testControlsForDispatch_dockedStackVisible() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final WindowState win = createWindowOnStack(null, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The app must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_freeformStackVisible() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final WindowState win = createWindowOnStack(null, WINDOWING_MODE_FREEFORM,
+                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
+        final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win);
+
+        // The app must not control any bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_dockedDividerControllerResizing() {
+        addWindow(TYPE_STATUS_BAR, "topBar");
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+        mDisplayContent.getDockedDividerController().setResizing(true);
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control any system bars.
+        assertNull(controls);
+    }
+
+    @Test
+    public void testControlsForDispatch_keyguard() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |= PRIVATE_FLAG_KEYGUARD;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the top bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    // TODO: adjust this test if we pretend to the app that it's still able to control it.
+    @Test
+    public void testControlsForDispatch_forceStatusBarVisibleTransparent() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+                PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the top bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    @Test
+    public void testControlsForDispatch_statusBarForceShowNavigation() {
+        addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |=
+                PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        addWindow(TYPE_NAVIGATION_BAR, "navBar");
+
+        final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
+
+        // The app must not control the navigation bar.
+        assertNotNull(controls);
+        assertEquals(1, controls.length);
+    }
+
+    private WindowState addWindow(int type, String name) {
+        final WindowState win = createWindow(null, type, name);
+        mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+        return win;
+    }
+
+    private InsetsSourceControl[] addAppWindowAndGetControlsForDispatch() {
+        return addWindowAndGetControlsForDispatch(addWindow(TYPE_APPLICATION, "app"));
+    }
+
+    private InsetsSourceControl[] addWindowAndGetControlsForDispatch(WindowState win) {
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
+        return mDisplayContent.getInsetsStateController().getControlsForDispatch(win);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 86ee75e..3e2e438 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -31,14 +31,15 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class InsetsSourceProviderTest extends WindowTestsBase {
 
     private InsetsSource mSource = new InsetsSource(TYPE_TOP_BAR);
@@ -53,7 +54,7 @@
 
     @Test
     public void testPostLayout() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         topBar.mHasSurface = true;
         mProvider.setWindow(topBar, null);
@@ -66,7 +67,7 @@
 
     @Test
     public void testPostLayout_invisible() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
         mProvider.onPostLayout();
@@ -76,7 +77,7 @@
 
     @Test
     public void testPostLayout_frameProvider() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar,
                 (displayFrames, windowState, rect) -> {
@@ -88,19 +89,32 @@
 
     @Test
     public void testUpdateControlForTarget() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
         mProvider.updateControlForTarget(target, false /* force */);
-        assertNotNull(mProvider.getControl());
+        assertNotNull(mProvider.getControl(target));
         mProvider.updateControlForTarget(null, false /* force */);
-        assertNull(mProvider.getControl());
+        assertNull(mProvider.getControl(target));
+    }
+
+    @Test
+    public void testUpdateControlForFakeTarget() {
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
+        topBar.getFrameLw().set(0, 0, 500, 100);
+        mProvider.setWindow(topBar, null);
+        mProvider.updateControlForFakeTarget(target);
+        assertNotNull(mProvider.getControl(target));
+        assertNull(mProvider.getControl(target).getLeash());
+        mProvider.updateControlForFakeTarget(null);
+        assertNull(mProvider.getControl(target));
     }
 
     @Test
     public void testInsetsModified() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
@@ -113,7 +127,7 @@
 
     @Test
     public void testInsetsModified_noControl() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         topBar.getFrameLw().set(0, 0, 500, 100);
         mProvider.setWindow(topBar, null);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 9fce78b..81ea32b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -23,13 +23,15 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
 import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.ViewRootImpl;
+import android.view.test.InsetsModeSession;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
@@ -37,90 +39,91 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 @SmallTest
+@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class InsetsStateControllerTest extends WindowTestsBase {
-    private static int sPreviousNewInsetsMode;
+    private static InsetsModeSession sInsetsModeSession;
 
     @BeforeClass
     public static void setUpOnce() {
-        // TODO: Make use of SettingsSession when it becomes feasible for this.
-        sPreviousNewInsetsMode = ViewRootImpl.sNewInsetsMode;
         // To let the insets provider control the insets visibility, the insets mode has to be
         // NEW_INSETS_MODE_FULL.
-        ViewRootImpl.sNewInsetsMode = NEW_INSETS_MODE_FULL;
+        sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
     }
 
     @AfterClass
     public static void tearDownOnce() {
-        ViewRootImpl.sNewInsetsMode = sPreviousNewInsetsMode;
+        sInsetsModeSession.close();
     }
 
     @Test
     @FlakyTest(bugId = 131005232)
     public void testStripForDispatch_notOwn() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR));
         assertNotNull(getController().getInsetsForDispatch(app).getSource(TYPE_TOP_BAR));
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testStripForDispatch_own() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .setWindow(topBar, null);
         topBar.setInsetProvider(getController().getSourceProvider(TYPE_TOP_BAR));
-        assertEquals(new InsetsState(), getController().getInsetsForDispatch(topBar));
+        final InsetsState state = getController().getInsetsForDispatch(topBar);
+        for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            assertNotEquals(TYPE_TOP_BAR, source.getType());
+        }
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testStripForDispatch_navBar() {
-        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState ime = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null);
         getController().getSourceProvider(TYPE_IME).setWindow(ime, null);
-        assertEquals(new InsetsState(), getController().getInsetsForDispatch(navBar));
+        assertEquals(0, getController().getInsetsForDispatch(navBar).getSourcesCount());
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testBarControllingWinChanged() {
-        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
         getController().getSourceProvider(TYPE_NAVIGATION_BAR).setWindow(navBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, app);
         InsetsSourceControl[] controls = getController().getControlsForDispatch(app);
         assertEquals(2, controls.length);
     }
 
-    @FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
     @Test
     public void testControlRevoked() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, null);
         assertNotNull(getController().getControlsForDispatch(app));
-        getController().onBarControllingWindowChanged(null);
+        getController().onBarControlTargetChanged(null, null);
         assertNull(getController().getControlsForDispatch(app));
     }
 
     @FlakyTest(bugId = 124088319)
     @Test
     public void testControlRevoked_animation() {
-        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "parentWindow");
-        final WindowState app = createWindow(null, TYPE_APPLICATION, "parentWindow");
+        final WindowState topBar = createWindow(null, TYPE_APPLICATION, "topBar");
+        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         getController().getSourceProvider(TYPE_TOP_BAR).setWindow(topBar, null);
-        getController().onBarControllingWindowChanged(app);
+        getController().onBarControlTargetChanged(app, null);
         assertNotNull(getController().getControlsForDispatch(app));
         topBar.cancelAnimation();
         assertNull(getController().getControlsForDispatch(app));
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index c52c8d7..2d0416d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -48,7 +48,7 @@
     @Before
     public void setUp() throws Exception {
         mSurfaces = new SurfaceControlMocker();
-        mLetterbox = new Letterbox(mSurfaces);
+        mLetterbox = new Letterbox(mSurfaces, () -> mock(SurfaceControl.Transaction.class));
         mTransaction = mock(SurfaceControl.Transaction.class);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
index efd468f..e9c2263 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PinnedStackControllerTest.java
@@ -66,8 +66,8 @@
 
         verify(mIPinnedStackListener).onImeVisibilityChanged(false, 0);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(false, 0);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(false), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(false));
         verify(mIPinnedStackListener).onActionsChanged(any());
         verify(mIPinnedStackListener).onMinimizedStateChanged(anyBoolean());
 
@@ -75,8 +75,8 @@
 
         mWm.setShelfHeight(true, SHELF_HEIGHT);
         verify(mIPinnedStackListener).onShelfVisibilityChanged(true, SHELF_HEIGHT);
-        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), any(), any(), eq(false),
-                eq(true), anyInt());
+        verify(mIPinnedStackListener).onMovementBoundsChanged(any(), eq(false),
+                eq(true));
         verify(mIPinnedStackListener, never()).onImeVisibilityChanged(anyBoolean(), anyInt());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index fb4e330..94abd34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -62,6 +62,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.util.MutableLong;
 import android.util.SparseBooleanArray;
 
@@ -1003,7 +1004,6 @@
     public void testRecentsComponent_allowApiAccessWithoutPermissions() {
         doReturn(PackageManager.PERMISSION_DENIED).when(mService)
                 .checkGetTasksPermission(anyString(), anyInt(), anyInt());
-
         // Set the recents component and ensure that the following calls do not fail
         mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
         doTestRecentTasksApis(true /* expectNoSecurityException */);
@@ -1031,7 +1031,7 @@
         assertSecurityException(expectCallable,
                 () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
         assertSecurityException(expectCallable,
-                () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+                () -> mService.animateResizePinnedStack(INVALID_STACK_ID, new Rect(), -1));
         assertSecurityException(expectCallable,
                 () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
                         new Rect()));
@@ -1289,10 +1289,10 @@
         @Override
         void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
                 int ignoreWindowingMode, ArrayList<ActivityDisplay> activityDisplays,
-                int callingUid, boolean allowed) {
+                int callingUid, boolean allowed, boolean crossUser, ArraySet<Integer> profileIds) {
             mLastAllowed = allowed;
             super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
-                    callingUid, allowed);
+                    callingUid, allowed, crossUser, profileIds);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 9ca0180..f792b0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -26,13 +26,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
+import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -44,9 +48,12 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
 import android.app.ActivityManager.TaskSnapshot;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.util.SparseBooleanArray;
@@ -88,8 +95,8 @@
             doReturn(mDisplayContent).when(mWm.mRoot).getDisplayContent(anyInt());
         }
         when(mMockRunner.asBinder()).thenReturn(new Binder());
-        mController = new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
-                DEFAULT_DISPLAY);
+        mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
+                DEFAULT_DISPLAY));
     }
 
     @Test
@@ -133,11 +140,11 @@
     @Test
     public void testIncludedApps_expectTargetAndVisible() {
         mWm.setRecentsAnimationController(mController);
-        final ActivityStack homStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
         final AppWindowToken homeAppWindow =
                 new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
-                        .setStack(homStack)
+                        .setStack(homeStack)
                         .setCreateTask(true)
                         .build()
                         .mAppWindowToken;
@@ -157,6 +164,102 @@
     }
 
     @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Ensure that we are animating the app and wallpaper target
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertTrue(mController.isAnimatingWallpaper(wallpaperWindowToken));
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        mDisplayContent.getConfiguration().windowConfiguration.setRotation(
+                mDisplayContent.getRotation());
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+
+        // Cancel the animation and ensure the controller is still running
+        wallpaperWindowToken.cancelAnimation();
+        assertTrue(mController.isAnimatingTask(appWindow.getTask()));
+        assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken));
+        verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */);
+    }
+
+    @Test
+    public void testFinish_expectTargetAndWallpaperAdaptersRemoved() {
+        mWm.setRecentsAnimationController(mController);
+        final ActivityStack homeStack = mDisplayContent.mAcitvityDisplay.getOrCreateStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+        final AppWindowToken homeAppWindow =
+                new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
+                        .setStack(homeStack)
+                        .setCreateTask(true)
+                        .build()
+                        .mAppWindowToken;
+        final WindowState hwin1 = createWindow(null, TYPE_BASE_APPLICATION, homeAppWindow, "hwin1");
+        homeAppWindow.addWindow(hwin1);
+        final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, appWindow, "win1");
+        appWindow.addWindow(win1);
+        final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
+                mock(IBinder.class), true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+
+        // Start and finish the animation
+        mController.initialize(ACTIVITY_TYPE_HOME, new SparseBooleanArray());
+        mController.startAnimation();
+        // Reset at this point since we may remove adapters that couldn't be created
+        reset(mController);
+        mController.cleanupAnimation(REORDER_MOVE_TO_TOP);
+
+        // Ensure that we remove the task (home & app) and wallpaper adapters
+        verify(mController, times(2)).removeAnimation(any());
+        verify(mController, times(1)).removeWallpaperAnimation(any());
+    }
+
+    @Test
     public void testDeferCancelAnimation() throws Exception {
         mWm.setRecentsAnimationController(mController);
         final AppWindowToken appWindow = createAppWindowToken(mDisplayContent,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index dcc295c..b4ccd50 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -56,6 +56,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Build/Install/Run:
@@ -63,6 +64,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class RecentsAnimationTest extends ActivityTestsBase {
 
     private static final int TEST_USER_ID = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 3e05dcc..3b9c3bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -19,7 +19,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -27,10 +29,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.IInterface;
 import android.platform.test.annotations.Presubmit;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -96,9 +100,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(new Point(50, 100), app.position);
@@ -201,9 +208,12 @@
         mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
         final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                 ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+        final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
         final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                 ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+        verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                finishedCaptor.capture());
         assertEquals(1, appsCaptor.getValue().length);
         assertEquals(mMockLeash, appsCaptor.getValue()[0].leash);
     }
@@ -237,9 +247,12 @@
             mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
             final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
                     ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
             final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
                     ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
-            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), finishedCaptor.capture());
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
             assertEquals(1, appsCaptor.getValue().length);
             final RemoteAnimationTarget app = appsCaptor.getValue()[0];
             assertEquals(RemoteAnimationTarget.MODE_CHANGING, app.mode);
@@ -264,6 +277,66 @@
         }
     }
 
+    @Test
+    public void testWallpaperIncluded_expectTarget() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
+    @Test
+    public void testWallpaperAnimatorCanceled_expectAnimationKeepsRunning() throws Exception {
+        final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, mDisplayContent, true /* ownerCanManageAppTokens */);
+        spyOn(mDisplayContent.mWallpaperController);
+        doReturn(true).when(mDisplayContent.mWallpaperController).isWallpaperVisible();
+        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+        mDisplayContent.mOpeningApps.add(win.mAppToken);
+        try {
+            final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mAppToken,
+                    new Point(50, 100), new Rect(50, 100, 150, 150), null).mAdapter;
+            adapter.startAnimation(mMockLeash, mMockTransaction, mFinishedCallback);
+            mController.goodToGo();
+            mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+            final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+                    ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+            final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+                    ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+            verify(mMockRunner).onAnimationStart(appsCaptor.capture(), wallpapersCaptor.capture(),
+                    finishedCaptor.capture());
+            assertEquals(1, wallpapersCaptor.getValue().length);
+
+            // Cancel the wallpaper window animator and ensure the runner is not canceled
+            wallpaperWindowToken.cancelAnimation();
+            verify(mMockRunner, never()).onAnimationCancelled();
+        } finally {
+            mDisplayContent.mOpeningApps.clear();
+        }
+    }
+
     private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) {
         verify(binder, atLeast(0)).asBinder();
         verifyNoMoreInteractions(binder);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 539a79c..aa97de72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -25,6 +25,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.TYPE_VIRTUAL;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -61,6 +62,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
+import android.view.DisplayInfo;
 
 import androidx.test.filters.MediumTest;
 
@@ -69,6 +71,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -82,6 +85,7 @@
  */
 @MediumTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class RootActivityContainerTests extends ActivityTestsBase {
     private ActivityStack mFullscreenStack;
 
@@ -575,7 +579,7 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithUserKeyLocked() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+        final TestActivityDisplay secondDisplay = createNewActivityDisplay();
         mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
 
         doReturn(true).when(secondDisplay).supportsSystemDecorations();
@@ -601,7 +605,7 @@
     @Test
     public void testStartSecondaryHomeOnDisplayWithoutSysDecorations() {
         // Create secondary displays.
-        final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay());
+        final TestActivityDisplay secondDisplay = createNewActivityDisplay();
         mRootActivityContainer.addChild(secondDisplay, POSITION_TOP);
         doReturn(false).when(secondDisplay).supportsSystemDecorations();
 
@@ -818,6 +822,41 @@
     }
 
     /**
+     * Test that {@link RootActivityContainer#getLaunchStack} with the real caller id will get the
+     * expected stack when requesting the activity launch on the secondary display.
+     */
+    @Test
+    public void testGetLaunchStackWithRealCallerId() {
+        // Create a non-system owned virtual display.
+        final DisplayInfo info = new DisplayInfo();
+        mSupervisor.mService.mContext.getDisplay().getDisplayInfo(info);
+        info.type = TYPE_VIRTUAL;
+        info.ownerUid = 100;
+        final TestActivityDisplay secondaryDisplay = TestActivityDisplay.create(mSupervisor, info);
+        mRootActivityContainer.addChild(secondaryDisplay, POSITION_TOP);
+
+        // Create an activity with specify the original launch pid / uid.
+        final ActivityRecord r = new ActivityBuilder(mService).setLaunchedFromPid(200)
+                .setLaunchedFromUid(200).build();
+
+        // Simulate ActivityStarter to find a launch stack for requesting the activity to launch
+        // on the secondary display with realCallerId.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(secondaryDisplay.mDisplayId);
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        doReturn(true).when(mSupervisor).canPlaceEntityOnDisplay(secondaryDisplay.mDisplayId,
+                300 /* test realCallerPid */, 300 /* test realCallerUid */, r.info);
+        final ActivityStack result = mRootActivityContainer.getLaunchStack(r, options,
+                null /* task */, true /* onTop */, null, 300 /* test realCallerPid */,
+                300 /* test realCallerUid */);
+
+        // Assert that the stack is returned as expected.
+        assertNotNull(result);
+        assertEquals("The display ID of the stack should same as secondary display ",
+                secondaryDisplay.mDisplayId, result.mDisplayId);
+    }
+
+    /**
      * Mock {@link RootActivityContainer#resolveHomeActivity} for returning consistent activity
      * info for test cases (the original implementation will resolve from the real package manager).
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index dc96480..3e316f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 
 import androidx.test.filters.MediumTest;
 
@@ -45,6 +46,8 @@
 @Presubmit
 public class RunningTasksTest extends ActivityTestsBase {
 
+    private static final ArraySet<Integer> PROFILE_IDS = new ArraySet<>();
+
     private RunningTasks mRunningTasks;
 
     @Before
@@ -77,7 +80,8 @@
         final int numFetchTasks = 5;
         ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
         mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
-                displays, -1 /* callingUid */, true /* allowed */);
+                displays, -1 /* callingUid */, true /* allowed */, true /*crossUser */,
+                PROFILE_IDS);
         assertThat(tasks).hasSize(numFetchTasks);
         for (int i = 0; i < numFetchTasks; i++) {
             assertEquals(numTasks - i - 1, tasks.get(i).id);
@@ -87,7 +91,8 @@
         // and does not crash
         tasks.clear();
         mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
-                displays, -1 /* callingUid */, true /* allowed */);
+                displays, -1 /* callingUid */, true /* allowed */, true /* crossUser */,
+                PROFILE_IDS);
         assertThat(tasks).hasSize(numTasks);
         for (int i = 0; i < numTasks; i++) {
             assertEquals(numTasks - i - 1, tasks.get(i).id);
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 83dd69a..8d2a79b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -185,11 +185,6 @@
     }
 
     @Override
-    public SurfaceControl.Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
-        return this;
-    }
-
-    @Override
     public SurfaceControl.Transaction setSecure(SurfaceControl sc, boolean isSecure) {
         return this;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 340e741..2b1c4ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -138,6 +138,19 @@
     }
 
     @Test
+    public void testCancelWithNullFinishCallbackAnimation() {
+        SurfaceAnimator animator = new SurfaceAnimator(mAnimatable, null, mWm);
+        animator.startAnimation(mTransaction, mSpec, true /* hidden */);
+        assertTrue(animator.isAnimating());
+        assertNotNull(animator.getAnimation());
+        animator.cancelAnimation();
+        assertFalse(animator.isAnimating());
+        assertNull(animator.getAnimation());
+        verify(mSpec).onAnimationCancelled(any());
+        verify(mTransaction).remove(eq(mAnimatable.mLeash));
+    }
+
+    @Test
     public void testDelayingAnimationStart() {
         mAnimatable.mSurfaceAnimator.startDelayingAnimationStart();
         mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 1ad0e00..5a4d399 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -56,6 +56,7 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
+import android.os.StrictMode;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.view.InputChannel;
@@ -73,6 +74,7 @@
 import com.android.server.appop.AppOpsService;
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -99,6 +101,7 @@
     static int sNextTaskId = 100;
 
     private final AtomicBoolean mCurrentMessagesProcessed = new AtomicBoolean(false);
+    private static final int[] TEST_USER_PROFILE_IDS = {};
 
     private Context mContext;
     private StaticMockitoSession mMockitoSession;
@@ -228,7 +231,7 @@
                 new AMTestInjector(mContext, mHandlerThread), mHandlerThread);
         spyOn(mAmService);
         doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager();
-        doNothing().when(mAmService).grantEphemeralAccessLocked(
+        doNothing().when(mAmService).grantImplicitAccess(
                 anyInt(), any(), anyInt(), anyInt());
 
         // ActivityManagerInternal
@@ -253,18 +256,17 @@
         mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
         mWMPolicy = new TestWindowManagerPolicy(this::getWindowManagerService,
                 mPowerManagerWrapper);
+        // Suppress StrictMode violation (DisplayWindowSettings) to avoid log flood.
+        DisplayThread.getHandler().post(StrictMode::allowThreadDiskWritesMask);
         mWmService = WindowManagerService.main(
-                mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new);
+                mContext, mImService, false, false, mWMPolicy, mAtmService, StubTransaction::new,
+                () -> mock(Surface.class), (unused) -> new MockSurfaceControlBuilder());
         spyOn(mWmService);
 
         // Setup factory classes to prevent calls to native code.
         mTransaction = spy(StubTransaction.class);
         // Return a spied Transaction class than can be used to verify calls.
         mWmService.mTransactionFactory = () -> mTransaction;
-        // Return a SurfaceControl.Builder class that creates mocked SurfaceControl instances.
-        mWmService.mSurfaceBuilderFactory = (unused) -> new MockSurfaceControlBuilder();
-        // Return mocked Surface instances.
-        mWmService.mSurfaceFactory = () -> mock(Surface.class);
         mWmService.mSurfaceAnimationRunner = new SurfaceAnimationRunner(
                 null, null, mTransaction, mWmService.mPowerManagerInternal);
 
@@ -424,6 +426,11 @@
             doReturn(AppOpsManager.MODE_DEFAULT)
                     .when(aos).noteOperation(anyInt(), anyInt(), anyString());
 
+            // UserManagerService
+            final UserManagerService ums = mock(UserManagerService.class);
+            doReturn(ums).when(this).getUserManager();
+            doReturn(TEST_USER_PROFILE_IDS).when(ums).getProfileIds(anyInt(), eq(true));
+
             setUsageStatsManager(LocalServices.getService(UsageStatsManagerInternal.class));
             ams.mActivityTaskManager = this;
             ams.mAtmInternal = mInternal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 19fd93fe..6e41118 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -271,6 +271,54 @@
         waitForCallback(singleTaskDisplayDrawnLatch);
     }
 
+    @Test
+    public void testSingleTaskDisplayEmpty() throws Exception {
+        final Instrumentation instrumentation = getInstrumentation();
+
+        final CountDownLatch activityViewReadyLatch = new CountDownLatch(1);
+        final CountDownLatch activityViewDestroyedLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayDrawnLatch = new CountDownLatch(1);
+        final CountDownLatch singleTaskDisplayEmptyLatch = new CountDownLatch(1);
+
+        registerTaskStackChangedListener(new TaskStackListener() {
+            @Override
+            public void onSingleTaskDisplayDrawn(int displayId) throws RemoteException {
+                singleTaskDisplayDrawnLatch.countDown();
+            }
+            @Override
+            public void onSingleTaskDisplayEmpty(int displayId)
+                    throws RemoteException {
+                singleTaskDisplayEmptyLatch.countDown();
+            }
+        });
+        final ActivityViewTestActivity activity =
+                (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class);
+        final ActivityView activityView = activity.getActivityView();
+        activityView.setCallback(new ActivityView.StateCallback() {
+            @Override
+            public void onActivityViewReady(ActivityView view) {
+                activityViewReadyLatch.countDown();
+            }
+
+            @Override
+            public void onActivityViewDestroyed(ActivityView view) {
+                activityViewDestroyedLatch.countDown();
+            }
+        });
+        waitForCallback(activityViewReadyLatch);
+
+        final Context context = instrumentation.getContext();
+        Intent intent = new Intent(context, ActivityInActivityView.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        activityView.startActivity(intent);
+        waitForCallback(singleTaskDisplayDrawnLatch);
+        assertEquals(1, singleTaskDisplayEmptyLatch.getCount());
+
+        activityView.release();
+        waitForCallback(activityViewDestroyedLatch);
+        waitForCallback(singleTaskDisplayEmptyLatch);
+    }
+
     /**
      * Starts the provided activity and returns the started instance.
      */
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java
new file mode 100644
index 0000000..e0c314f
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerGlobalLockRule.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Holds {@link WindowManagerGlobalLock} for test methods (including before and after) to prevent
+ * the race condition with other threads (e.g. {@link com.android.server.DisplayThread},
+ * {@link com.android.server.AnimationThread}, {@link com.android.server.UiThread}).
+ */
+class WindowManagerGlobalLockRule implements WindowTestRunner.MethodWrapper {
+
+    private final SystemServicesTestRule mSystemServicesTestRule;
+    private volatile boolean mIsLocked;
+
+    WindowManagerGlobalLockRule(SystemServicesTestRule systemServiceTestRule) {
+        mSystemServicesTestRule = systemServiceTestRule;
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        if (description.getAnnotation(NoGlobalLock.class) == null) {
+            return new StatementWrapper(base);
+        }
+        return base;
+    }
+
+    @Override
+    public FrameworkMethod apply(FrameworkMethod base) {
+        if (base.getAnnotation(NoGlobalLock.class) == null) {
+            return new FrameworkMethodWrapper(base);
+        }
+        return base;
+    }
+
+    void waitUntilHandlersIdle() {
+        if (!mIsLocked) {
+            mSystemServicesTestRule.waitUntilWindowManagerHandlersIdle();
+            return;
+        }
+
+        waitForLocked(mSystemServicesTestRule::waitUntilWindowManagerHandlersIdle);
+    }
+
+    void runWithScissors(Handler handler, Runnable r, long timeout) {
+        if (!mIsLocked) {
+            handler.runWithScissors(r, timeout);
+            return;
+        }
+
+        waitForLocked(() -> handler.runWithScissors(r, timeout));
+    }
+
+    /**
+     * If the test holds the lock, we need to invoke {@link Object#wait} to release it so other
+     * threads won't be blocked when we are waiting.
+     */
+    private void waitForLocked(Runnable r) {
+        final Object lock = mSystemServicesTestRule.getWindowManagerService().mGlobalLock;
+        final AtomicBoolean done = new AtomicBoolean(false);
+
+        AsyncTask.SERIAL_EXECUTOR.execute(() -> {
+            r.run();
+            synchronized (lock) {
+                lock.notifyAll();
+                done.set(true);
+            }
+        });
+
+        synchronized (lock) {
+            if (!done.get()) {
+                try {
+                    lock.wait();
+                } catch (InterruptedException impossible) {
+                }
+            }
+        }
+    }
+
+    /** Wraps methods annotated with {@link org.junit.Test}. */
+    private class StatementWrapper extends Statement {
+        final Statement mBase;
+
+        StatementWrapper(Statement base) {
+            mBase = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            try {
+                synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
+                    mIsLocked = true;
+                    mBase.evaluate();
+                }
+            } finally {
+                mIsLocked = false;
+            }
+        }
+    }
+
+    /** Wraps methods annotated with {@link org.junit.Before} or {@link org.junit.After}. */
+    private class FrameworkMethodWrapper extends FrameworkMethod {
+
+        FrameworkMethodWrapper(FrameworkMethod base) {
+            super(base.getMethod());
+        }
+
+        @Override
+        public Object invokeExplosively(Object target, Object... params) throws Throwable {
+            try {
+                synchronized (mSystemServicesTestRule.getWindowManagerService().mGlobalLock) {
+                    mIsLocked = true;
+                    return super.invokeExplosively(target, params);
+                }
+            } finally {
+                mIsLocked = false;
+            }
+        }
+    }
+
+    /**
+     * If the test method is annotated with {@link NoGlobalLock}, the rule
+     * {@link WindowManagerGlobalLockRule} won't apply to the method.
+     */
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface NoGlobalLock {
+        String reason() default "";
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
new file mode 100644
index 0000000..86f0f8b
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerSettingsTests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Test for {@link WindowManagerService.SettingsObserver}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:WindowManagerSettingsTests
+ */
+@SmallTest
+public class WindowManagerSettingsTests extends WindowTestsBase {
+
+    @Test
+    public void testForceDesktopModeOnExternalDisplays() {
+        try (SettingsSession forceDesktopModeSession = new
+                SettingsSession(DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS)) {
+            final boolean forceDesktopMode = !forceDesktopModeSession.getSetting();
+            final Uri forceDesktopModeUri = forceDesktopModeSession.setSetting(forceDesktopMode);
+            mWm.mSettingsObserver.onChange(false, forceDesktopModeUri);
+
+            assertEquals(mWm.mForceDesktopModeOnExternalDisplays, forceDesktopMode);
+        }
+    }
+
+    @Test
+    public void testFreeformWindow() {
+        try (SettingsSession freeformWindowSession = new
+                SettingsSession(DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT)) {
+            final boolean freeformWindow = !freeformWindowSession.getSetting();
+            final Uri freeformWindowUri = freeformWindowSession.setSetting(freeformWindow);
+            mWm.mSettingsObserver.onChange(false, freeformWindowUri);
+
+            assertEquals(mWm.mAtmService.mSupportsFreeformWindowManagement, freeformWindow);
+        }
+    }
+
+    @Test
+    public void testForceResizableMode() {
+        try (SettingsSession forceResizableSession = new
+                SettingsSession(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES)) {
+            final boolean forceResizableMode = !forceResizableSession.getSetting();
+            final Uri forceResizableUri =  forceResizableSession.setSetting(forceResizableMode);
+            mWm.mSettingsObserver.onChange(false, forceResizableUri);
+
+            assertEquals(mWm.mAtmService.mForceResizableActivities, forceResizableMode);
+        }
+    }
+
+    private class SettingsSession implements AutoCloseable {
+
+        private static final int SETTING_VALUE_OFF = 0;
+        private static final int SETTING_VALUE_ON = 1;
+        private final String mSettingName;
+        private final int mInitialValue;
+
+        SettingsSession(String name) {
+            mSettingName = name;
+            mInitialValue = getSetting() ? SETTING_VALUE_ON : SETTING_VALUE_OFF;
+        }
+
+        boolean getSetting() {
+            return Settings.Global.getInt(getContentResolver(), mSettingName, 0) != 0;
+        }
+
+        Uri setSetting(boolean enable) {
+            Settings.Global.putInt(getContentResolver(), mSettingName,
+                    enable ? SETTING_VALUE_ON : SETTING_VALUE_OFF);
+            return Settings.Global.getUriFor(mSettingName);
+        }
+
+        private ContentResolver getContentResolver() {
+            return getInstrumentation().getTargetContext().getContentResolver();
+        }
+
+        @Override
+        public void close() {
+            setSetting(mInitialValue == SETTING_VALUE_ON);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index b731628..f41f126 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -78,6 +78,7 @@
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.LinkedList;
 
@@ -89,6 +90,7 @@
  */
 @SmallTest
 @Presubmit
+@RunWith(WindowTestRunner.class)
 public class WindowStateTests extends WindowTestsBase {
     private static int sPreviousNewInsetsMode;
 
@@ -111,11 +113,8 @@
         // TODO: Let the insets source with new mode keep the visibility control, and remove this
         // setup code. Now mTopFullscreenOpaqueWindowState will take back the control of insets
         // visibility.
-        // Hold the lock to protect the mock from accesssing by other threads.
-        synchronized (mWm.mGlobalLock) {
-            spyOn(mDisplayContent);
-            doNothing().when(mDisplayContent).layoutAndAssignWindowLayersIfNeeded();
-        }
+        spyOn(mDisplayContent);
+        doNothing().when(mDisplayContent).layoutAndAssignWindowLayersIfNeeded();
     }
 
     @Test
@@ -404,7 +403,7 @@
         assertTrue(topBar.isVisible());
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .setWindow(topBar, null);
-        mDisplayContent.getInsetsStateController().onBarControllingWindowChanged(app);
+        mDisplayContent.getInsetsStateController().onBarControlTargetChanged(app, app);
         mDisplayContent.getInsetsStateController().getSourceProvider(TYPE_TOP_BAR)
                 .onInsetsModified(app, new InsetsSource(TYPE_TOP_BAR));
         waitUntilHandlersIdle();
@@ -527,27 +526,25 @@
         final float OFFSET_SUM =
                 PARENT_WINDOW_OFFSET + DISPLAY_IN_PARENT_WINDOW_OFFSET + WINDOW_OFFSET;
 
-        synchronized (mWm.mGlobalLock) {
-            final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
+        final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
 
-            final DisplayContent dc = createNewDisplay();
-            win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
-            dc.reparentDisplayContent(win0, win0.getSurfaceControl());
-            dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
+        final DisplayContent dc = createNewDisplay();
+        win0.getFrameLw().offsetTo(PARENT_WINDOW_OFFSET, 0);
+        dc.reparentDisplayContent(win0, win0.getSurfaceControl());
+        dc.updateLocation(win0, DISPLAY_IN_PARENT_WINDOW_OFFSET, 0);
 
-            final float[] values = new float[9];
-            final Matrix matrix = new Matrix();
-            final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
-            final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
-            win1.mHasSurface = true;
-            win1.mSurfaceControl = mock(SurfaceControl.class);
-            win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
-            win1.updateSurfacePosition(t);
-            win1.getTransformationMatrix(values, matrix);
+        final float[] values = new float[9];
+        final Matrix matrix = new Matrix();
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
+        win1.mHasSurface = true;
+        win1.mSurfaceControl = mock(SurfaceControl.class);
+        win1.getFrameLw().offsetTo(WINDOW_OFFSET, 0);
+        win1.updateSurfacePosition(t);
+        win1.getTransformationMatrix(values, matrix);
 
-            matrix.getValues(values);
-            assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
-            assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
-        }
+        matrix.getValues(values);
+        assertEquals(OFFSET_SUM, values[Matrix.MTRANS_X], 0f);
+        assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java
new file mode 100644
index 0000000..2b801e2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestRunner.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A runner with support to bind additional operations with test method tightly.
+ *
+ * @see MethodWrapperRule
+ */
+public class WindowTestRunner extends AndroidJUnit4ClassRunner {
+    private final List<FrameworkMethod> mBefores;
+    private final List<FrameworkMethod> mAfters;
+
+    public WindowTestRunner(Class<?> klass) throws InitializationError {
+        super(klass);
+        mBefores = getTestClass().getAnnotatedMethods(Before.class);
+        mAfters = getTestClass().getAnnotatedMethods(After.class);
+    }
+
+    @Override
+    protected Statement methodInvoker(FrameworkMethod method, Object test) {
+        return wrapStatement(super.methodInvoker(method, test), method, test);
+    }
+
+    private Statement wrapStatement(Statement statement, FrameworkMethod method, Object target) {
+        for (MethodWrapper wrapper : getMethodWrappers(target)) {
+            statement = wrapper.apply(statement, describeChild(method));
+        }
+        return statement;
+    }
+
+    /**
+     * Constructs the test statement with {@link Before}.
+     *
+     * @param method The test method.
+     * @param target The instance of test class.
+     * @param statement The next statement. It is usually the test method.
+     */
+    @Override
+    protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) {
+        if (mBefores.isEmpty()) {
+            return statement;
+        }
+
+        final List<FrameworkMethod> befores = new ArrayList<>(mBefores.size());
+        for (FrameworkMethod before : mBefores) {
+            befores.add(wrapMethod(before, target));
+        }
+        return new RunBefores(statement, befores, target);
+    }
+
+    /**
+     * Constructs the test statement with {@link After}.
+     *
+     * @param method The test method.
+     * @param target The instance of test class.
+     * @param statement The next statement. If there are "before" methods, then it is the
+     *                  before-statement for the next test.
+     */
+    @Override
+    protected Statement withAfters(FrameworkMethod method, Object target, Statement statement) {
+        if (mAfters.isEmpty()) {
+            return statement;
+        }
+
+        final List<FrameworkMethod> afters = new ArrayList<>(mAfters.size());
+        for (FrameworkMethod after : mAfters) {
+            afters.add(wrapMethod(after, target));
+        }
+        return new RunAfters(statement, afters, target);
+    }
+
+    private FrameworkMethod wrapMethod(FrameworkMethod method, Object target) {
+        for (MethodWrapper wrapper : getMethodWrappers(target)) {
+            method = wrapper.apply(method);
+        }
+        return method;
+    }
+
+    private List<MethodWrapper> getMethodWrappers(Object target) {
+        return getTestClass().getAnnotatedFieldValues(
+                target, MethodWrapperRule.class, MethodWrapper.class);
+    }
+
+    /**
+     * If a {@link TestRule} is annotated with this, it can ensure the operation of the rule runs
+     * with the test method on the same path and thread.
+     * <p>
+     * The traditional {@link org.junit.Rule} may run on another thread if timeout is set. And if
+     * the rule will hold a lock which will be used in test method, it will cause deadlock such as
+     * "Instr: androidx.test.runner.AndroidJUnitRunner" and "Time-limited test" wait for each other.
+     * <p>
+     * This annotation only takes effect if the test runner is {@link WindowTestRunner}.
+     *
+     * @see org.junit.internal.runners.statements.FailOnTimeout
+     * @see org.junit.runners.BlockJUnit4ClassRunner#methodBlock
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ ElementType.FIELD, ElementType.METHOD })
+    @interface MethodWrapperRule {}
+
+    /**
+     * The interface to support wrapping test method, including {@link Before} and {@link After}.
+     */
+    interface MethodWrapper extends TestRule {
+        FrameworkMethod apply(FrameworkMethod base);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index d1cf1c3..8930e5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -98,6 +98,10 @@
     @Rule
     public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
 
+    @WindowTestRunner.MethodWrapperRule
+    public final WindowManagerGlobalLockRule mLockRule =
+            new WindowManagerGlobalLockRule(mSystemServicesTestRule);
+
     @BeforeClass
     public static void setUpOnceBase() {
         AttributeCache.init(getInstrumentation().getTargetContext());
@@ -205,7 +209,7 @@
      * Waits until the main handler for WM has processed all messages.
      */
     void waitUntilHandlersIdle() {
-        mSystemServicesTestRule.waitUntilWindowManagerHandlersIdle();
+        mLockRule.waitUntilHandlersIdle();
     }
 
     private WindowToken createWindowToken(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 7d7c398..2105ab0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -51,6 +51,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.function.Function;
 
 /**
  * Tests for the {@link DisplayContent#assignChildLayers(SurfaceControl.Transaction)} method.
@@ -131,7 +132,8 @@
         }
     }
 
-    private static class HierarchyRecordingBuilderFactory implements SurfaceBuilderFactory {
+    private static class HierarchyRecordingBuilderFactory implements Function<SurfaceSession,
+                SurfaceControl.Builder> {
         private LayerRecordingTransaction mTransaction;
 
         HierarchyRecordingBuilderFactory(LayerRecordingTransaction transaction) {
@@ -139,7 +141,7 @@
         }
 
         @Override
-        public SurfaceControl.Builder make(SurfaceSession s) {
+        public SurfaceControl.Builder apply(SurfaceSession s) {
             final LayerRecordingTransaction transaction = mTransaction;
             return new HierarchyRecorder(s, transaction);
         }
@@ -153,7 +155,7 @@
         // which is after construction of the DisplayContent, meaning the HierarchyRecorder
         // would miss construction of the top-level layers.
         mTransaction = new LayerRecordingTransaction();
-        mWm.mSurfaceBuilderFactory = new HierarchyRecordingBuilderFactory(mTransaction);
+        mWm.mSurfaceControlFactory = new HierarchyRecordingBuilderFactory(mTransaction);
         mWm.mTransactionFactory = () -> mTransaction;
     }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsProto.java b/services/usage/java/com/android/server/usage/UsageStatsProto.java
index 3e88d93..6d3f416 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsProto.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsProto.java
@@ -606,4 +606,37 @@
 
         proto.flush();
     }
+
+    // TODO: move to UsageStatsProtoV2
+    static void readPendingEvents(InputStream in, List<UsageEvents.Event> events)
+            throws IOException {
+        final ProtoInputStream proto = new ProtoInputStream(in);
+        final List<String> stringPool = new ArrayList<>();
+        final IntervalStats tmpStatsObj = new IntervalStats();
+        while (true) {
+            switch (proto.nextField()) {
+                case (int) IntervalStatsProto.PENDING_EVENTS:
+                    loadEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, stringPool);
+                    break;
+                case ProtoInputStream.NO_MORE_FIELDS:
+                    final int eventCount = tmpStatsObj.events.size();
+                    for (int i = 0; i < eventCount; i++) {
+                        events.add(tmpStatsObj.events.get(i));
+                    }
+                    return;
+            }
+        }
+    }
+
+    // TODO: move to UsageStatsProtoV2
+    static void writePendingEvents(OutputStream out, List<UsageEvents.Event> events)
+            throws IOException {
+        final ProtoOutputStream proto = new ProtoOutputStream(out);
+        final IntervalStats tmpStatsObj = new IntervalStats();
+        final int eventCount = events.size();
+        for (int i = 0; i < eventCount; i++) {
+            writeEvent(proto, IntervalStatsProto.PENDING_EVENTS, tmpStatsObj, events.get(i));
+        }
+        proto.flush();
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 2bdeddf..090623e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -18,14 +18,18 @@
 
 import static android.app.usage.UsageEvents.Event.CHOOSER_ACTION;
 import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
+import static android.app.usage.UsageEvents.Event.DEVICE_EVENT_PACKAGE_NAME;
 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
+import static android.app.usage.UsageEvents.Event.USER_STOPPED;
+import static android.app.usage.UsageEvents.Event.USER_UNLOCKED;
 import static android.app.usage.UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
 import static android.app.usage.UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
@@ -70,8 +74,10 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.internal.content.PackageMonitor;
@@ -81,11 +87,20 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
@@ -106,11 +121,17 @@
     private static final long TEN_SECONDS = 10 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
-    private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
+    static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
 
     private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
+    private static final File USAGE_STATS_LEGACY_DIR = new File(
+            Environment.getDataSystemDirectory(), "usagestats");
+    // For migration purposes, indicates whether to keep the legacy usage stats directory or not
+    // STOPSHIP: b/138323140 this should be false on launch
+    private static final boolean KEEP_LEGACY_DIR = true;
+
     private static final char TOKEN_DELIMITER = '/';
 
     // Handler message types.
@@ -119,6 +140,7 @@
     static final int MSG_REMOVE_USER = 2;
     static final int MSG_UID_STATE_CHANGED = 3;
     static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
+    static final int MSG_UNLOCKED_USER = 5;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -132,10 +154,8 @@
     DevicePolicyManagerInternal mDpmInternal;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
+    private final SparseBooleanArray mUserUnlockedStates = new SparseBooleanArray();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
-    private File mUsageStatsDir;
-    long mRealTimeSnapshot;
-    long mSystemTimeSnapshot;
     int mUsageSource;
 
     /** Manages the standby state of apps. */
@@ -144,6 +164,8 @@
     /** Manages app time limit observers */
     AppTimeLimitController mAppTimeLimit;
 
+    // A map maintaining a queue of events to be reported per user.
+    private final SparseArray<LinkedList<Event>> mReportedEvents = new SparseArray<>();
     final SparseArray<ArraySet<String>> mUsageReporters = new SparseArray();
     final SparseArray<ActivityData> mVisibleActivities = new SparseArray();
 
@@ -165,7 +187,7 @@
                             SystemClock.elapsedRealtime());
                     event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
                     event.mPackage = packageName;
-                    mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+                    reportEventOrAddToQueue(userId, event);
                 }
 
                 @Override
@@ -223,30 +245,14 @@
                 }, mHandler.getLooper());
 
         mAppStandby.addListener(mStandbyChangeListener);
-        File systemDataDir = new File(Environment.getDataDirectory(), "system");
-        mUsageStatsDir = new File(systemDataDir, "usagestats");
-        mUsageStatsDir.mkdirs();
-        if (!mUsageStatsDir.exists()) {
-            throw new IllegalStateException("Usage stats directory does not exist: "
-                    + mUsageStatsDir.getAbsolutePath());
-        }
 
         IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
         filter.addAction(Intent.ACTION_USER_STARTED);
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
-        synchronized (mLock) {
-            cleanUpRemovedUsersLocked();
-        }
-
-        mRealTimeSnapshot = SystemClock.elapsedRealtime();
-        mSystemTimeSnapshot = System.currentTimeMillis();
-
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
-        // Make sure we initialize the data, in case job scheduler needs it early.
-        getUserDataAndInitializeIfNeededLocked(UserHandle.USER_SYSTEM, mSystemTimeSnapshot);
     }
 
     @Override
@@ -275,6 +281,85 @@
         }
     }
 
+    @Override
+    public void onStartUser(UserInfo userInfo) {
+        // Create an entry in the user state map to indicate that the user has been started but
+        // not necessarily unlocked. This will ensure that reported events are flushed to disk
+        // event if the user is never unlocked (following the logic in #flushToDiskLocked)
+        mUserState.put(userInfo.id, null);
+        super.onStartUser(userInfo);
+    }
+
+    @Override
+    public void onUnlockUser(@NonNull UserInfo userInfo) {
+        mHandler.obtainMessage(MSG_UNLOCKED_USER, userInfo.id, 0).sendToTarget();
+        super.onUnlockUser(userInfo);
+    }
+
+    @Override
+    public void onStopUser(@NonNull UserInfo userInfo) {
+        synchronized (mLock) {
+            // User was started but never unlocked so no need to report a user stopped event
+            if (!mUserUnlockedStates.get(userInfo.id)) {
+                persistPendingEventsLocked(userInfo.id);
+                super.onStopUser(userInfo);
+                return;
+            }
+
+            // Report a user stopped event before persisting all stats to disk via the user service
+            final Event event = new Event(USER_STOPPED, SystemClock.elapsedRealtime());
+            event.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
+            reportEvent(event, userInfo.id);
+            final UserUsageStatsService userService = mUserState.get(userInfo.id);
+            if (userService != null) {
+                userService.userStopped();
+            }
+            mUserUnlockedStates.put(userInfo.id, false);
+            mUserState.put(userInfo.id, null); // release the service (mainly for GC)
+        }
+        super.onStopUser(userInfo);
+    }
+
+    private void onUserUnlocked(int userId) {
+        synchronized (mLock) {
+            // Create a user unlocked event to report
+            final Event unlockEvent = new Event(USER_UNLOCKED, SystemClock.elapsedRealtime());
+            unlockEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
+
+            migrateStatsToSystemCeIfNeededLocked(userId);
+
+            // Read pending reported events from disk and merge them with those stored in memory
+            final LinkedList<Event> pendingEvents = new LinkedList<>();
+            loadPendingEventsLocked(userId, pendingEvents);
+            final LinkedList<Event> eventsInMem = mReportedEvents.get(userId);
+            if (eventsInMem != null) {
+                pendingEvents.addAll(eventsInMem);
+            }
+            boolean needToFlush = !pendingEvents.isEmpty();
+
+            mUserUnlockedStates.put(userId, true);
+            final UserUsageStatsService userService = getUserDataAndInitializeIfNeededLocked(
+                    userId, System.currentTimeMillis());
+            userService.userUnlocked(System.currentTimeMillis());
+            // Process all the pending reported events
+            while (pendingEvents.peek() != null) {
+                reportEvent(pendingEvents.poll(), userId);
+            }
+            reportEvent(unlockEvent, userId);
+
+            // Remove all the stats stored in memory and in system DE.
+            mReportedEvents.remove(userId);
+            deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats"));
+            // Force a flush to disk for the current user to ensure important events are persisted.
+            // Note: there is a very very small chance that the system crashes between deleting
+            // the stats above from DE and persisting them to CE here in which case we will lose
+            // those events that were in memory and deleted from DE. (b/139836090)
+            if (needToFlush) {
+                userService.persistActiveStats();
+            }
+        }
+    }
+
     private DevicePolicyManagerInternal getDpmInternal() {
         if (mDpmInternal == null) {
             mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
@@ -350,33 +435,6 @@
         return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
     }
 
-    private void cleanUpRemovedUsersLocked() {
-        final List<UserInfo> users = mUserManager.getUsers(true);
-        if (users == null || users.size() == 0) {
-            throw new IllegalStateException("There can't be no users");
-        }
-
-        ArraySet<String> toDelete = new ArraySet<>();
-        String[] fileNames = mUsageStatsDir.list();
-        if (fileNames == null) {
-            // No users to delete.
-            return;
-        }
-
-        toDelete.addAll(Arrays.asList(fileNames));
-
-        final int userCount = users.size();
-        for (int i = 0; i < userCount; i++) {
-            final UserInfo userInfo = users.get(i);
-            toDelete.remove(Integer.toString(userInfo.id));
-        }
-
-        final int deleteCount = toDelete.size();
-        for (int i = 0; i < deleteCount; i++) {
-            deleteRecursively(new File(mUsageStatsDir, toDelete.valueAt(i)));
-        }
-    }
-
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -385,7 +443,7 @@
             }
         }
 
-        if (!f.delete()) {
+        if (f.exists() && !f.delete()) {
             Slog.e(TAG, "Failed to delete " + f);
         }
     }
@@ -394,43 +452,115 @@
             long currentTimeMillis) {
         UserUsageStatsService service = mUserState.get(userId);
         if (service == null) {
-            service = new UserUsageStatsService(getContext(), userId,
-                    new File(mUsageStatsDir, Integer.toString(userId)), this);
-            service.init(currentTimeMillis);
+            final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
+                    "usagestats");
+            service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this);
+            if (mUserUnlockedStates.get(userId)) {
+                service.init(currentTimeMillis);
+            }
             mUserState.put(userId, service);
         }
         return service;
     }
 
-    /**
-     * This should be the only way to get the time from the system.
-     */
-    private long checkAndGetTimeLocked() {
-        final long actualSystemTime = System.currentTimeMillis();
-        final long actualRealtime = SystemClock.elapsedRealtime();
-        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
-        final long diffSystemTime = actualSystemTime - expectedSystemTime;
-        if (Math.abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS
-                && ENABLE_TIME_CHANGE_CORRECTION) {
-            // The time has changed.
-            Slog.i(TAG, "Time changed in UsageStats by " + (diffSystemTime / 1000) + " seconds");
-            final int userCount = mUserState.size();
-            for (int i = 0; i < userCount; i++) {
-                final UserUsageStatsService service = mUserState.valueAt(i);
-                service.onTimeChanged(expectedSystemTime, actualSystemTime);
-            }
-            mRealTimeSnapshot = actualRealtime;
-            mSystemTimeSnapshot = actualSystemTime;
+    private void migrateStatsToSystemCeIfNeededLocked(int userId) {
+        final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId),
+                "usagestats");
+        if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) {
+            throw new IllegalStateException("Usage stats directory does not exist: "
+                    + usageStatsDir.getAbsolutePath());
         }
-        return actualSystemTime;
+        // Check if the migrated status file exists - if not, migrate usage stats.
+        final File migrated = new File(usageStatsDir, "migrated");
+        if (migrated.exists()) {
+            try (BufferedReader reader = new BufferedReader(new FileReader(migrated))) {
+                final int previousVersion = Integer.parseInt(reader.readLine());
+                // UsageStatsDatabase.BACKUP_VERSION was 4 when usage stats were migrated to CE.
+                if (previousVersion >= 4) {
+                    deleteLegacyDir(userId);
+                    return;
+                }
+                // If migration logic needs to be changed in a future version, do it here.
+            } catch (NumberFormatException | IOException e) {
+                Slog.e(TAG, "Failed to read migration status file, possibly corrupted.");
+                deleteRecursively(usageStatsDir);
+                if (usageStatsDir.exists()) {
+                    Slog.e(TAG, "Unable to delete usage stats CE directory.");
+                    throw new RuntimeException(e);
+                } else {
+                    // Make the directory again since previous migration was not complete
+                    if (!usageStatsDir.mkdirs() && !usageStatsDir.exists()) {
+                        throw new IllegalStateException("Usage stats directory does not exist: "
+                                + usageStatsDir.getAbsolutePath());
+                    }
+                }
+            }
+        }
+
+        Slog.i(TAG, "Starting migration to system CE for user " + userId);
+        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
+        if (legacyUserDir.exists()) {
+            copyRecursively(usageStatsDir, legacyUserDir);
+        }
+        // Create a status file to indicate that the migration to CE has been completed.
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(migrated))) {
+            writer.write(Integer.toString(UsageStatsDatabase.BACKUP_VERSION));
+            writer.write("\n");
+            writer.flush();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write migrated status file");
+            throw new RuntimeException(e);
+        }
+        Slog.i(TAG, "Finished migration to system CE for user " + userId);
+
+        // Migration was successful - delete the legacy directory
+        deleteLegacyDir(userId);
     }
 
-    /**
-     * Assuming the event's timestamp is measured in milliseconds since boot,
-     * convert it to a system wall time.
-     */
-    private void convertToSystemTimeLocked(Event event) {
-        event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
+    private static void copyRecursively(final File parent, File f) {
+        final File[] files = f.listFiles();
+        if (files == null) {
+            try {
+                Files.copy(f.toPath(), new File(parent, f.getName()).toPath(),
+                        StandardCopyOption.REPLACE_EXISTING);
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to move usage stats file : " + f.toString());
+                throw new RuntimeException(e);
+            }
+            return;
+        }
+
+        for (int i = files.length - 1; i >= 0; i--) {
+            File newParent = parent;
+            if (files[i].isDirectory()) {
+                newParent = new File(parent, files[i].getName());
+                final boolean mkdirSuccess = newParent.mkdirs();
+                if (!mkdirSuccess && !newParent.exists()) {
+                    throw new IllegalStateException(
+                            "Failed to create usage stats directory during migration: "
+                            + newParent.getAbsolutePath());
+                }
+            }
+            copyRecursively(newParent, files[i]);
+        }
+    }
+
+    private void deleteLegacyDir(int userId) {
+        final File legacyUserDir = new File(USAGE_STATS_LEGACY_DIR, Integer.toString(userId));
+        if (!KEEP_LEGACY_DIR) {
+            deleteRecursively(legacyUserDir);
+            if (legacyUserDir.exists()) {
+                Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats "
+                        + "dir for user " + userId);
+            }
+            // If all users have been migrated, delete the parent legacy usage stats directory
+            if (USAGE_STATS_LEGACY_DIR.list() != null
+                    && USAGE_STATS_LEGACY_DIR.list().length == 0) {
+                if (!USAGE_STATS_LEGACY_DIR.delete()) {
+                    Slog.w(TAG, "Error occurred while attempting to delete legacy usage stats dir");
+                }
+            }
+        }
     }
 
     /**
@@ -463,14 +593,97 @@
         mHandler.sendEmptyMessage(MSG_FLUSH_TO_DISK);
     }
 
+    private void loadPendingEventsLocked(int userId, LinkedList<Event> pendingEvents) {
+        final File usageStatsDeDir = new File(Environment.getDataSystemDeDirectory(userId),
+                "usagestats");
+        final File[] pendingEventsFiles = usageStatsDeDir.listFiles();
+        if (pendingEventsFiles == null || pendingEventsFiles.length == 0) {
+            return;
+        }
+        Arrays.sort(pendingEventsFiles);
+
+        for (int i = 0; i < pendingEventsFiles.length; i++) {
+            final AtomicFile af = new AtomicFile(pendingEventsFiles[i]);
+            try {
+                try (FileInputStream in = af.openRead()) {
+                    UsageStatsProto.readPendingEvents(in, pendingEvents);
+                }
+            } catch (IOException e) {
+                // Even if one file read fails, exit here to keep all events in order on disk -
+                // they will be read and processed the next time user is unlocked.
+                Slog.e(TAG, "Could not read " + pendingEventsFiles[i] + " for user " + userId);
+                pendingEvents.clear();
+                return;
+            }
+        }
+    }
+
+    private void persistPendingEventsLocked(int userId) {
+        final LinkedList<Event> pendingEvents = mReportedEvents.get(userId);
+        if (pendingEvents == null || pendingEvents.isEmpty()) {
+            return;
+        }
+
+        final File usageStatsDeDir = new File(Environment.getDataSystemDeDirectory(userId),
+                "usagestats");
+        if (!usageStatsDeDir.mkdirs() && !usageStatsDeDir.exists()) {
+            throw new IllegalStateException("Usage stats DE directory does not exist: "
+                    + usageStatsDeDir.getAbsolutePath());
+        }
+        final File pendingEventsFile = new File(usageStatsDeDir,
+                "pendingevents_" + System.currentTimeMillis());
+        final AtomicFile af = new AtomicFile(pendingEventsFile);
+        FileOutputStream fos = null;
+        try {
+            fos = af.startWrite();
+            UsageStatsProto.writePendingEvents(fos, pendingEvents);
+            af.finishWrite(fos);
+            fos = null;
+            pendingEvents.clear();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write " + pendingEventsFile.getAbsolutePath()
+                    + " for user " + userId);
+        } finally {
+            af.failWrite(fos); // when fos is null (successful write), this will no-op
+        }
+    }
+
+    private void reportEventOrAddToQueue(int userId, Event event) {
+        synchronized (mLock) {
+            if (mUserUnlockedStates.get(userId)) {
+                mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+                return;
+            }
+
+            final LinkedList<Event> events = mReportedEvents.get(userId, new LinkedList<>());
+            events.add(event);
+            if (mReportedEvents.get(userId) == null) {
+                mReportedEvents.put(userId, events);
+            }
+            if (events.size() == 1) {
+                // Every time a file is persisted to disk, mReportedEvents is cleared for this user
+                // so trigger a flush to disk every time the first event has been added.
+                mHandler.sendEmptyMessageDelayed(MSG_FLUSH_TO_DISK, FLUSH_INTERVAL);
+            }
+        }
+    }
+
     /**
      * Called by the Binder stub.
      */
     void reportEvent(Event event, int userId) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
+            // This should never be called directly when the user is locked
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.wtf(TAG, "Failed to report event for locked user " + userId
+                        + " (" + event.mPackage + "/" + event.mClass
+                        + " eventType:" + event.mEventType
+                        + " instanceId:" + event.mInstanceId + ")");
+                return;
+            }
+
+            final long timeNow = System.currentTimeMillis();
             final long elapsedRealtime = SystemClock.elapsedRealtime();
-            convertToSystemTimeLocked(event);
 
             if (event.mPackage != null
                     && mPackageManagerInternal.isPackageEphemeral(userId, event.mPackage)) {
@@ -581,7 +794,7 @@
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
                 Event copy = new Event(event);
-                reportEvent(copy, mUserState.keyAt(i));
+                reportEventOrAddToQueue(mUserState.keyAt(i), copy);
             }
         }
     }
@@ -597,6 +810,7 @@
             // The FLUSH_TO_DISK event is an internal event, it will not show up in IntervalStats'
             // EventList.
             Event event = new Event(FLUSH_TO_DISK, SystemClock.elapsedRealtime());
+            event.mPackage = DEVICE_EVENT_PACKAGE_NAME;
             reportEventToAllUserId(event);
             flushToDiskLocked();
         }
@@ -611,7 +825,6 @@
             mUserState.remove(userId);
             mAppStandby.onUserRemoved(userId);
             mAppTimeLimit.onUserRemoved(userId);
-            cleanUpRemovedUsersLocked();
         }
     }
 
@@ -621,13 +834,13 @@
     List<UsageStats> queryUsageStats(int userId, int bucketType, long beginTime, long endTime,
             boolean obfuscateInstantApps) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query usage stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
             List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
             if (list == null) {
                 return null;
@@ -643,7 +856,6 @@
                     }
                 }
             }
-
             return list;
         }
     }
@@ -654,13 +866,13 @@
     List<ConfigurationStats> queryConfigurationStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query configuration stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
             return service.queryConfigurationStats(bucketType, beginTime, endTime);
         }
     }
@@ -671,13 +883,13 @@
     List<EventStats> queryEventStats(int userId, int bucketType, long beginTime,
             long endTime) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query event stats for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
             return service.queryEventStats(bucketType, beginTime, endTime);
         }
     }
@@ -688,13 +900,13 @@
     UsageEvents queryEvents(int userId, long beginTime, long endTime,
             boolean shouldObfuscateInstantApps) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query events for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
             return service.queryEvents(beginTime, endTime, shouldObfuscateInstantApps);
         }
     }
@@ -705,21 +917,17 @@
     UsageEvents queryEventsForPackage(int userId, long beginTime, long endTime,
             String packageName, boolean includeTaskRoot) {
         synchronized (mLock) {
-            final long timeNow = checkAndGetTimeLocked();
-            if (!validRange(timeNow, beginTime, endTime)) {
+            if (!mUserUnlockedStates.get(userId)) {
+                Slog.w(TAG, "Failed to query package events for locked user " + userId);
                 return null;
             }
 
             final UserUsageStatsService service =
-                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+                    getUserDataAndInitializeIfNeededLocked(userId, System.currentTimeMillis());
             return service.queryEventsForPackage(beginTime, endTime, packageName, includeTaskRoot);
         }
     }
 
-    private static boolean validRange(long currentTime, long beginTime, long endTime) {
-        return beginTime <= currentTime && beginTime < endTime;
-    }
-
     private String buildFullToken(String packageName, String token) {
         final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1);
         sb.append(packageName);
@@ -731,9 +939,14 @@
     private void flushToDiskLocked() {
         final int userCount = mUserState.size();
         for (int i = 0; i < userCount; i++) {
-            UserUsageStatsService service = mUserState.valueAt(i);
+            final int userId = mUserState.keyAt(i);
+            if (!mUserUnlockedStates.get(userId)) {
+                persistPendingEventsLocked(userId);
+                continue;
+            }
+            UserUsageStatsService service = mUserState.get(userId);
             service.persistActiveStats();
-            mAppStandby.flushToDisk(mUserState.keyAt(i));
+            mAppStandby.flushToDisk(userId);
         }
         mAppStandby.flushDurationsToDisk();
 
@@ -833,6 +1046,18 @@
                     } else if ("appstandby".equals(arg)) {
                         mAppStandby.dumpState(args, pw);
                         return;
+                    } else if ("stats-directory".equals(arg)) {
+                        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                        final int userId;
+                        try {
+                            userId = Integer.valueOf(args[i + 1]);
+                        } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+                            ipw.println("invalid user specified.");
+                            return;
+                        }
+                        ipw.println(new File(Environment.getDataSystemCeDirectory(userId),
+                                "usagestats").getAbsolutePath());
+                        return;
                     } else if (arg != null && !arg.startsWith("-")) {
                         // Anything else that doesn't start with '-' is a pkg to filter
                         pkg = arg;
@@ -887,7 +1112,9 @@
                 case MSG_FLUSH_TO_DISK:
                     flushToDisk();
                     break;
-
+                case MSG_UNLOCKED_USER:
+                    onUserUnlocked(msg.arg1);
+                    break;
                 case MSG_REMOVE_USER:
                     onUserRemoved(msg.arg1);
                     break;
@@ -1368,7 +1595,7 @@
             event.mAction = action;
             event.mContentType = contentType;
             event.mContentAnnotations = annotations;
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1640,7 +1867,7 @@
                 event.mTaskRootPackage = taskRoot.getPackageName();
                 event.mTaskRootClass = taskRoot.getClassName();
             }
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1652,7 +1879,7 @@
 
             Event event = new Event(eventType, SystemClock.elapsedRealtime());
             event.mPackage = packageName;
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1665,7 +1892,7 @@
             Event event = new Event(CONFIGURATION_CHANGE, SystemClock.elapsedRealtime());
             event.mPackage = "android";
             event.mConfiguration = new Configuration(config);
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1679,7 +1906,7 @@
             Event event = new Event(NOTIFICATION_INTERRUPTION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mNotificationChannelId = channelId.intern();
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1692,7 +1919,7 @@
             Event event = new Event(SHORTCUT_INVOCATION, SystemClock.elapsedRealtime());
             event.mPackage = packageName.intern();
             event.mShortcutId = shortcutId.intern();
-            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
+            reportEventOrAddToQueue(userId, event);
         }
 
         @Override
@@ -1749,11 +1976,16 @@
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
-            // Check to ensure that only user 0's data is b/r for now
             synchronized (mLock) {
+                if (!mUserUnlockedStates.get(user)) {
+                    Slog.w(TAG, "Failed to get backup payload for locked user " + user);
+                    return null;
+                }
+
+                // Check to ensure that only user 0's data is b/r for now
                 if (user == UserHandle.USER_SYSTEM) {
-                    final UserUsageStatsService userStats =
-                            getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
+                    final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
+                            user, System.currentTimeMillis());
                     return userStats.getBackupPayload(key);
                 } else {
                     return null;
@@ -1764,9 +1996,14 @@
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
             synchronized (mLock) {
+                if (!mUserUnlockedStates.get(user)) {
+                    Slog.w(TAG, "Failed to apply restored payload for locked user " + user);
+                    return;
+                }
+
                 if (user == UserHandle.USER_SYSTEM) {
-                    final UserUsageStatsService userStats =
-                            getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
+                    final UserUsageStatsService userStats = getUserDataAndInitializeIfNeededLocked(
+                            user, System.currentTimeMillis());
                     userStats.applyRestoredPayload(key, payload);
                 }
             }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 11c0e4a..1560b9e 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -76,6 +76,8 @@
     private final String mLogPrefix;
     private String mLastBackgroundedPackage;
     private final int mUserId;
+    private long mRealTimeSnapshot;
+    private long mSystemTimeSnapshot;
 
     private static final long[] INTERVAL_LENGTH = new long[] {
             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
@@ -101,6 +103,8 @@
         mListener = listener;
         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
         mUserId = userId;
+        mRealTimeSnapshot = SystemClock.elapsedRealtime();
+        mSystemTimeSnapshot = System.currentTimeMillis();
     }
 
     void init(final long currentTimeMillis) {
@@ -156,12 +160,50 @@
         }
     }
 
-    void onTimeChanged(long oldTime, long newTime) {
+    void userUnlocked(long currentTimeMillis) {
+        init(currentTimeMillis);
+    }
+
+    void userStopped() {
+        // Flush events to disk immediately to guarantee persistence.
+        persistActiveStats();
+    }
+
+    private void onTimeChanged(long oldTime, long newTime) {
         persistActiveStats();
         mDatabase.onTimeChanged(newTime - oldTime);
         loadActiveStats(newTime);
     }
 
+    /**
+     * This should be the only way to get the time from the system.
+     */
+    private long checkAndGetTimeLocked() {
+        final long actualSystemTime = System.currentTimeMillis();
+        if (!UsageStatsService.ENABLE_TIME_CHANGE_CORRECTION) {
+            return actualSystemTime;
+        }
+        final long actualRealtime = SystemClock.elapsedRealtime();
+        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+        final long diffSystemTime = actualSystemTime - expectedSystemTime;
+        if (Math.abs(diffSystemTime) > UsageStatsService.TIME_CHANGE_THRESHOLD_MILLIS) {
+            // The time has changed.
+            Slog.i(TAG, mLogPrefix + "Time changed in by " + (diffSystemTime / 1000) + " seconds");
+            onTimeChanged(expectedSystemTime, actualSystemTime);
+            mRealTimeSnapshot = actualRealtime;
+            mSystemTimeSnapshot = actualSystemTime;
+        }
+        return actualSystemTime;
+    }
+
+    /**
+     * Assuming the event's timestamp is measured in milliseconds since boot,
+     * convert it to a system wall time.
+     */
+    private void convertToSystemTimeLocked(Event event) {
+        event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
+    }
+
     void reportEvent(Event event) {
         if (DEBUG) {
             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
@@ -169,6 +211,9 @@
                     + eventToString(event.mEventType));
         }
 
+        checkAndGetTimeLocked();
+        convertToSystemTimeLocked(event);
+
         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
             // Need to rollover
             rolloverStats(event.mTimeStamp);
@@ -289,6 +334,10 @@
                 }
             };
 
+    private static boolean validRange(long currentTime, long beginTime, long endTime) {
+        return beginTime <= currentTime && beginTime < endTime;
+    }
+
     /**
      * Generic query method that selects the appropriate IntervalStats for the specified time range
      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
@@ -361,19 +410,31 @@
     }
 
     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
     }
 
     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
     }
 
     List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
     }
 
     UsageEvents queryEvents(final long beginTime, final long endTime,
                             boolean obfuscateInstantApps) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         final ArraySet<String> names = new ArraySet<>();
         List<Event> results = queryStats(INTERVAL_DAILY,
                 beginTime, endTime, new StatCombiner<Event>() {
@@ -419,6 +480,9 @@
 
     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
             final String packageName, boolean includeTaskRoot) {
+        if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
+            return null;
+        }
         final ArraySet<String> names = new ArraySet<>();
         names.add(packageName);
         final List<Event> results = queryStats(INTERVAL_DAILY,
@@ -1021,10 +1085,12 @@
     }
 
     byte[] getBackupPayload(String key){
+        checkAndGetTimeLocked();
         return mDatabase.getBackupPayload(key);
     }
 
     void applyRestoredPayload(String key, byte[] payload){
+        checkAndGetTimeLocked();
         mDatabase.applyRestoredPayload(key, payload);
     }
 }
diff --git a/services/usb/java/com/android/server/usb/UsbPermissionManager.java b/services/usb/java/com/android/server/usb/UsbPermissionManager.java
index 14c7c7c..ef9ee73 100644
--- a/services/usb/java/com/android/server/usb/UsbPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbPermissionManager.java
@@ -60,6 +60,10 @@
         }
     }
 
+    @NonNull UsbUserPermissionManager getPermissionsForUser(@NonNull UserHandle user) {
+        return getPermissionsForUser(user.getIdentifier());
+    }
+
     void remove(@NonNull UserHandle userToRemove) {
         synchronized (mPermissionsByUser) {
             mPermissionsByUser.remove(userToRemove.getIdentifier());
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 5e136bb..d7b6b5d 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -46,6 +46,8 @@
 import android.service.usb.UsbSettingsAccessoryPreferenceProto;
 import android.service.usb.UsbSettingsDevicePreferenceProto;
 import android.service.usb.UserPackageProto;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
@@ -70,6 +72,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.net.ProtocolException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -102,10 +105,20 @@
     @GuardedBy("mLock")
     private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>();
 
+    /** Maps DeviceFilter to set of UserPackages not to ask for launch preference anymore */
+    @GuardedBy("mLock")
+    private final ArrayMap<DeviceFilter, ArraySet<UserPackage>> mDevicePreferenceDeniedMap =
+            new ArrayMap<>();
+
     /** Maps AccessoryFilter to user preferred application package */
     @GuardedBy("mLock")
     private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>();
 
+    /** Maps AccessoryFilter to set of UserPackages not to ask for launch preference anymore */
+    @GuardedBy("mLock")
+    private final ArrayMap<AccessoryFilter, ArraySet<UserPackage>> mAccessoryPreferenceDeniedMap =
+            new ArrayMap<>();
+
     private final Object mLock = new Object();
 
     /**
@@ -248,11 +261,11 @@
     }
 
     /**
-     * Remove all defaults for a user.
+     * Remove all defaults and denied packages for a user.
      *
-     * @param userToRemove The user the defaults belong to.
+     * @param userToRemove The user
      */
-    void removeAllDefaultsForUser(@NonNull UserHandle userToRemove) {
+    void removeUser(@NonNull UserHandle userToRemove) {
         synchronized (mLock) {
             boolean needToPersist = false;
             Iterator<Map.Entry<DeviceFilter, UserPackage>> devicePreferenceIt = mDevicePreferenceMap
@@ -277,6 +290,28 @@
                 }
             }
 
+            int numEntries = mDevicePreferenceDeniedMap.size();
+            for (int i = 0; i < numEntries; i++) {
+                ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.valueAt(i);
+                for (int j = userPackages.size() - 1; j >= 0; j--) {
+                    if (userPackages.valueAt(j).user.equals(userToRemove)) {
+                        userPackages.removeAt(j);
+                        needToPersist = true;
+                    }
+                }
+            }
+
+            numEntries = mAccessoryPreferenceDeniedMap.size();
+            for (int i = 0; i < numEntries; i++) {
+                ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.valueAt(i);
+                for (int j = userPackages.size() - 1; j >= 0; j--) {
+                    if (userPackages.valueAt(j).user.equals(userToRemove)) {
+                        userPackages.removeAt(j);
+                        needToPersist = true;
+                    }
+                }
+            }
+
             if (needToPersist) {
                 scheduleWriteSettingsLocked();
             }
@@ -284,7 +319,7 @@
     }
 
     private void readPreference(XmlPullParser parser)
-            throws XmlPullParserException, IOException {
+            throws IOException, XmlPullParserException {
         String packageName = null;
 
         // If not set, assume it to be the parent profile
@@ -317,9 +352,70 @@
         XmlUtils.nextElement(parser);
     }
 
+    private void readPreferenceDeniedList(@NonNull XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        if (!XmlUtils.nextElementWithin(parser, outerDepth)) {
+            return;
+        }
+
+        if ("usb-device".equals(parser.getName())) {
+            DeviceFilter filter = DeviceFilter.read(parser);
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if ("user-package".equals(parser.getName())) {
+                    try {
+                        int userId = XmlUtils.readIntAttribute(parser, "user");
+
+                        String packageName = XmlUtils.readStringAttribute(parser, "package");
+                        if (packageName == null) {
+                            Slog.e(TAG, "Unable to parse package name");
+                        }
+
+                        ArraySet<UserPackage> set = mDevicePreferenceDeniedMap.get(filter);
+                        if (set == null) {
+                            set = new ArraySet<>();
+                            mDevicePreferenceDeniedMap.put(filter, set);
+                        }
+                        set.add(new UserPackage(packageName, UserHandle.of(userId)));
+                    } catch (ProtocolException e) {
+                        Slog.e(TAG, "Unable to parse user id", e);
+                    }
+                }
+            }
+        } else if ("usb-accessory".equals(parser.getName())) {
+            AccessoryFilter filter = AccessoryFilter.read(parser);
+
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if ("user-package".equals(parser.getName())) {
+                    try {
+                        int userId = XmlUtils.readIntAttribute(parser, "user");
+
+                        String packageName = XmlUtils.readStringAttribute(parser, "package");
+                        if (packageName == null) {
+                            Slog.e(TAG, "Unable to parse package name");
+                        }
+
+                        ArraySet<UserPackage> set = mAccessoryPreferenceDeniedMap.get(filter);
+                        if (set == null) {
+                            set = new ArraySet<>();
+                            mAccessoryPreferenceDeniedMap.put(filter, set);
+                        }
+                        set.add(new UserPackage(packageName, UserHandle.of(userId)));
+                    } catch (ProtocolException e) {
+                        Slog.e(TAG, "Unable to parse user id", e);
+                    }
+                }
+            }
+        }
+
+        while (parser.getDepth() > outerDepth) {
+            parser.nextTag(); // ignore unknown tags
+        }
+    }
+
     /**
      * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
-     * Should only by called by owner.
+     * Should only be called by owner.
      */
     @GuardedBy("mLock")
     private void upgradeSingleUserLocked() {
@@ -373,6 +469,8 @@
                 String tagName = parser.getName();
                 if ("preference".equals(tagName)) {
                     readPreference(parser);
+                } else if ("preference-denied-list".equals(tagName)) {
+                    readPreferenceDeniedList(parser);
                 } else {
                     XmlUtils.nextElement(parser);
                 }
@@ -436,6 +534,46 @@
                         serializer.endTag(null, "preference");
                     }
 
+                    int numEntries = mDevicePreferenceDeniedMap.size();
+                    for (int i = 0; i < numEntries; i++) {
+                        DeviceFilter filter = mDevicePreferenceDeniedMap.keyAt(i);
+                        ArraySet<UserPackage> userPackageSet = mDevicePreferenceDeniedMap
+                                .valueAt(i);
+                        serializer.startTag(null, "preference-denied-list");
+                        filter.write(serializer);
+
+                        int numUserPackages = userPackageSet.size();
+                        for (int j = 0; j < numUserPackages; j++) {
+                            UserPackage userPackage = userPackageSet.valueAt(j);
+                            serializer.startTag(null, "user-package");
+                            serializer.attribute(null, "user",
+                                    String.valueOf(getSerial(userPackage.user)));
+                            serializer.attribute(null, "package", userPackage.packageName);
+                            serializer.endTag(null, "user-package");
+                        }
+                        serializer.endTag(null, "preference-denied-list");
+                    }
+
+                    numEntries = mAccessoryPreferenceDeniedMap.size();
+                    for (int i = 0; i < numEntries; i++) {
+                        AccessoryFilter filter = mAccessoryPreferenceDeniedMap.keyAt(i);
+                        ArraySet<UserPackage> userPackageSet =
+                                mAccessoryPreferenceDeniedMap.valueAt(i);
+                        serializer.startTag(null, "preference-denied-list");
+                        filter.write(serializer);
+
+                        int numUserPackages = userPackageSet.size();
+                        for (int j = 0; j < numUserPackages; j++) {
+                            UserPackage userPackage = userPackageSet.valueAt(j);
+                            serializer.startTag(null, "user-package");
+                            serializer.attribute(null, "user",
+                                    String.valueOf(getSerial(userPackage.user)));
+                            serializer.attribute(null, "package", userPackage.packageName);
+                            serializer.endTag(null, "user-package");
+                        }
+                        serializer.endTag(null, "preference-denied-list");
+                    }
+
                     serializer.endTag(null, "settings");
                     serializer.endDocument();
 
@@ -834,6 +972,25 @@
     private void resolveActivity(@NonNull Intent intent, @NonNull ArrayList<ResolveInfo> matches,
             @Nullable ActivityInfo defaultActivity, @Nullable UsbDevice device,
             @Nullable UsbAccessory accessory) {
+        // Remove all matches which are on the denied list
+        ArraySet deniedPackages = null;
+        if (device != null) {
+            deniedPackages = mDevicePreferenceDeniedMap.get(new DeviceFilter(device));
+        } else if (accessory != null) {
+            deniedPackages = mAccessoryPreferenceDeniedMap.get(new AccessoryFilter(accessory));
+        }
+        if (deniedPackages != null) {
+            for (int i = matches.size() - 1; i >= 0; i--) {
+                ResolveInfo match = matches.get(i);
+                String packageName = match.activityInfo.packageName;
+                UserHandle user = UserHandle
+                        .getUserHandleForUid(match.activityInfo.applicationInfo.uid);
+                if (deniedPackages.contains(new UserPackage(packageName, user))) {
+                    matches.remove(i);
+                }
+            }
+        }
+
         // don't show the resolver activity if there are no choices available
         if (matches.size() == 0) {
             if (accessory != null) {
@@ -1076,6 +1233,156 @@
     }
 
     /**
+     * Add package to the denied for handling a device
+     *
+     * @param device the device to add to the denied
+     * @param packageNames the packages to not become handler
+     * @param user the user
+     */
+    void addDevicePackagesToDenied(@NonNull UsbDevice device, @NonNull String[] packageNames,
+            @NonNull UserHandle user) {
+        if (packageNames.length == 0) {
+            return;
+        }
+        DeviceFilter filter = new DeviceFilter(device);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages;
+            if (mDevicePreferenceDeniedMap.containsKey(filter)) {
+                userPackages = mDevicePreferenceDeniedMap.get(filter);
+            } else {
+                userPackages = new ArraySet<>();
+                mDevicePreferenceDeniedMap.put(filter, userPackages);
+            }
+
+            boolean shouldWrite = false;
+            for (String packageName : packageNames) {
+                UserPackage userPackage = new UserPackage(packageName, user);
+                if (!userPackages.contains(userPackage)) {
+                    userPackages.add(userPackage);
+                    shouldWrite = true;
+                }
+            }
+
+            if (shouldWrite) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+    }
+
+    /**
+     * Add package to the denied for handling a accessory
+     *
+     * @param accessory the accessory to add to the denied
+     * @param packageNames the packages to not become handler
+     * @param user the user
+     */
+    void addAccessoryPackagesToDenied(@NonNull UsbAccessory accessory,
+            @NonNull String[] packageNames, @NonNull UserHandle user) {
+        if (packageNames.length == 0) {
+            return;
+        }
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages;
+            if (mAccessoryPreferenceDeniedMap.containsKey(filter)) {
+                userPackages = mAccessoryPreferenceDeniedMap.get(filter);
+            } else {
+                userPackages = new ArraySet<>();
+                mAccessoryPreferenceDeniedMap.put(filter, userPackages);
+            }
+
+            boolean shouldWrite = false;
+            for (String packageName : packageNames) {
+                UserPackage userPackage = new UserPackage(packageName, user);
+                if (!userPackages.contains(userPackage)) {
+                    userPackages.add(userPackage);
+                    shouldWrite = true;
+                }
+            }
+
+            if (shouldWrite) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+    }
+
+    /**
+     * Remove UserPackage from the denied for handling a device
+     *
+     * @param device the device to remove denied packages from
+     * @param packageName the packages to remove
+     * @param user the user
+     */
+    void removeDevicePackagesFromDenied(@NonNull UsbDevice device, @NonNull String[] packageNames,
+            @NonNull UserHandle user) {
+        DeviceFilter filter = new DeviceFilter(device);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages = mDevicePreferenceDeniedMap.get(filter);
+
+            if (userPackages != null) {
+                boolean shouldWrite = false;
+                for (String packageName : packageNames) {
+                    UserPackage userPackage = new UserPackage(packageName, user);
+
+                    if (userPackages.contains(userPackage)) {
+                        userPackages.remove(userPackage);
+                        shouldWrite = true;
+
+                        if (userPackages.size() == 0) {
+                            mDevicePreferenceDeniedMap.remove(filter);
+                            break;
+                        }
+                    }
+                }
+
+                if (shouldWrite) {
+                    scheduleWriteSettingsLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove UserPackage from the denied for handling a accessory
+     *
+     * @param accessory the accessory to remove denied packages from
+     * @param packageName the packages to remove
+     * @param user the user
+     */
+    void removeAccessoryPackagesFromDenied(@NonNull UsbAccessory accessory,
+            @NonNull String[] packageNames, @NonNull UserHandle user) {
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+
+        synchronized (mLock) {
+            ArraySet<UserPackage> userPackages = mAccessoryPreferenceDeniedMap.get(filter);
+
+            if (userPackages != null) {
+                boolean shouldWrite = false;
+                for (String packageName : packageNames) {
+                    UserPackage userPackage = new UserPackage(packageName, user);
+
+                    if (userPackages.contains(userPackage)) {
+                        userPackages.remove(userPackage);
+                        shouldWrite = true;
+
+                        if (userPackages.size() == 0) {
+                            mAccessoryPreferenceDeniedMap.remove(filter);
+                            break;
+                        }
+                    }
+                }
+
+                if (shouldWrite) {
+                    scheduleWriteSettingsLocked();
+                }
+            }
+        }
+    }
+
+    /**
      * Set a package as default handler for a accessory.
      *
      * @param accessory The accessory that should be handled by default
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index cc1490e..ce6f592 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -361,6 +361,112 @@
     }
 
     @Override
+    public void addDevicePackagesToPreferenceDenied(UsbDevice device, String[] packageNames,
+            UserHandle user) {
+        device = Preconditions.checkNotNull(device);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .addDevicePackagesToDenied(device, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void addAccessoryPackagesToPreferenceDenied(UsbAccessory accessory,
+            String[] packageNames, UserHandle user) {
+        accessory = Preconditions.checkNotNull(accessory);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .addAccessoryPackagesToDenied(accessory, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void removeDevicePackagesFromPreferenceDenied(UsbDevice device, String[] packageNames,
+            UserHandle user) {
+        device = Preconditions.checkNotNull(device);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .removeDevicePackagesFromDenied(device, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void removeAccessoryPackagesFromPreferenceDenied(UsbAccessory accessory,
+            String[] packageNames, UserHandle user) {
+        accessory = Preconditions.checkNotNull(accessory);
+        packageNames = Preconditions.checkArrayElementsNotNull(packageNames, "packageNames");
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mSettingsManager.getSettingsForProfileGroup(user)
+                    .removeAccessoryPackagesFromDenied(accessory, packageNames, user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void setDevicePersistentPermission(UsbDevice device, int uid, UserHandle user,
+            boolean shouldBeGranted) {
+        device = Preconditions.checkNotNull(device);
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mPermissionManager.getPermissionsForUser(user).setDevicePersistentPermission(device,
+                    uid, shouldBeGranted);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void setAccessoryPersistentPermission(UsbAccessory accessory, int uid,
+            UserHandle user, boolean shouldBeGranted) {
+        accessory = Preconditions.checkNotNull(accessory);
+        user = Preconditions.checkNotNull(user);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mPermissionManager.getPermissionsForUser(user).setAccessoryPersistentPermission(
+                    accessory, uid, shouldBeGranted);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
     public boolean hasDevicePermission(UsbDevice device, String packageName) {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index fbd8782a..7b677ee 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -130,7 +130,7 @@
                 // it from all profile groups.
                 int numProfileGroups = mSettingsByProfileGroup.size();
                 for (int i = 0; i < numProfileGroups; i++) {
-                    mSettingsByProfileGroup.valueAt(i).removeAllDefaultsForUser(userToRemove);
+                    mSettingsByProfileGroup.valueAt(i).removeUser(userToRemove);
                 }
             }
         }
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index ec7567c..0cb64a3 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -24,24 +24,41 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbInterface;
 import android.hardware.usb.UsbManager;
+import android.os.AsyncTask;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Process;
 import android.os.UserHandle;
 import android.service.usb.UsbSettingsAccessoryPermissionProto;
 import android.service.usb.UsbSettingsDevicePermissionProto;
 import android.service.usb.UsbUserSettingsManagerProto;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
 import com.android.internal.util.dump.DualDumpOutputStream;
 
-import java.util.HashMap;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * UsbUserPermissionManager manages usb device or accessory access permissions.
@@ -49,25 +66,45 @@
  * @hide
  */
 class UsbUserPermissionManager {
-    private static final String LOG_TAG = UsbUserPermissionManager.class.getSimpleName();
+    private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     @GuardedBy("mLock")
-    /** Temporary mapping USB device name to list of UIDs with permissions for the device*/
-    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
-            new HashMap<>();
+    /** Mapping of USB device name to list of UIDs with permissions for the device
+     * Each entry lasts until device is disconnected*/
+    private final ArrayMap<String, SparseBooleanArray> mDevicePermissionMap =
+            new ArrayMap<>();
     @GuardedBy("mLock")
-    /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory*/
-    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
-            new HashMap<>();
+    /** Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
+     * Each entry lasts until accessory is disconnected*/
+    private final ArrayMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
+            new ArrayMap<>();
+
+    @GuardedBy("mLock")
+    /** Maps USB device to list of UIDs with persistent permissions for the device*/
+    private final ArrayMap<DeviceFilter, SparseBooleanArray>
+            mDevicePersistentPermissionMap = new ArrayMap<>();
+    @GuardedBy("mLock")
+    /** Maps Usb Accessory to list of UIDs with persistent permissions for the accessory*/
+    private final ArrayMap<AccessoryFilter, SparseBooleanArray>
+            mAccessoryPersistentPermissionMap = new ArrayMap<>();
 
     private final Context mContext;
     private final UserHandle mUser;
     private final UsbUserSettingsManager mUsbUserSettingsManager;
     private final boolean mDisablePermissionDialogs;
 
+    private final @NonNull AtomicFile mPermissionsFile;
+
     private final Object mLock = new Object();
 
+    /**
+     * If a async task to persist the mDevicePersistentPreferenceMap and
+     * mAccessoryPersistentPreferenceMap is currently scheduled.
+     */
+    @GuardedBy("mLock")
+    private boolean mIsCopyPermissionsScheduled;
+
     UsbUserPermissionManager(@NonNull Context context, @NonNull UserHandle user,
             @NonNull UsbUserSettingsManager usbUserSettingsManager) {
         mContext = context;
@@ -75,6 +112,14 @@
         mUsbUserSettingsManager = usbUserSettingsManager;
         mDisablePermissionDialogs = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_disableUsbPermissionDialogs);
+
+        mPermissionsFile = new AtomicFile(new File(
+                Environment.getUserSystemDirectory(user.getIdentifier()),
+                "usb_permissions.xml"), "usb-permissions");
+
+        synchronized (mLock) {
+            readPermissionsLocked();
+        }
     }
 
     /**
@@ -141,11 +186,24 @@
      * @param uid to check permission for
      * @return {@code true} if package with uid has permission
      */
-    boolean hasPermission(@NonNull UsbDevice device, int uid) {
+    boolean hasPermission(@NonNull UsbDevice device, @NonNull String packageName, int uid) {
+        if (isCameraDevicePresent(device)) {
+            if (!isCameraPermissionGranted(packageName, uid)) {
+                return false;
+            }
+        }
         synchronized (mLock) {
             if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                 return true;
             }
+            DeviceFilter filter = new DeviceFilter(device);
+            SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter);
+            if (permissionsForDevice != null) {
+                int idx = permissionsForDevice.indexOfKey(uid);
+                if (idx >= 0) {
+                    return permissionsForDevice.valueAt(idx);
+                }
+            }
             SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
             if (uidList == null) {
                 return false;
@@ -166,6 +224,15 @@
             if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                 return true;
             }
+            AccessoryFilter filter = new AccessoryFilter(accessory);
+            SparseBooleanArray permissionsForAccessory =
+                    mAccessoryPersistentPermissionMap.get(filter);
+            if (permissionsForAccessory != null) {
+                int idx = permissionsForAccessory.indexOfKey(uid);
+                if (idx >= 0) {
+                    return permissionsForAccessory.valueAt(idx);
+                }
+            }
             SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
             if (uidList == null) {
                 return false;
@@ -174,14 +241,236 @@
         }
     }
 
-    boolean hasPermission(UsbDevice device, String packageName, int uid) {
-        if (isCameraDevicePresent(device)) {
-            if (!isCameraPermissionGranted(packageName, uid)) {
-                return false;
+    void setDevicePersistentPermission(@NonNull UsbDevice device, int uid, boolean isGranted) {
+
+        boolean isChanged;
+        DeviceFilter filter = new DeviceFilter(device);
+        synchronized (mLock) {
+            SparseBooleanArray permissionsForDevice = mDevicePersistentPermissionMap.get(filter);
+            if (permissionsForDevice == null) {
+                permissionsForDevice = new SparseBooleanArray();
+                mDevicePersistentPermissionMap.put(filter, permissionsForDevice);
+            }
+            int idx = permissionsForDevice.indexOfKey(uid);
+            if (idx >= 0) {
+                isChanged = permissionsForDevice.valueAt(idx) != isGranted;
+                permissionsForDevice.setValueAt(idx, isGranted);
+            } else {
+                isChanged = true;
+                permissionsForDevice.put(uid, isGranted);
+            }
+
+            if (isChanged) {
+                scheduleWritePermissionLocked();
             }
         }
+    }
 
-        return hasPermission(device, uid);
+    void setAccessoryPersistentPermission(@NonNull UsbAccessory accessory, int uid,
+            boolean isGranted) {
+
+        boolean isChanged;
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+        synchronized (mLock) {
+            SparseBooleanArray permissionsForAccessory =
+                    mAccessoryPersistentPermissionMap.get(filter);
+            if (permissionsForAccessory == null) {
+                permissionsForAccessory = new SparseBooleanArray();
+                mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory);
+            }
+            int idx = permissionsForAccessory.indexOfKey(uid);
+            if (idx >= 0) {
+                isChanged = permissionsForAccessory.valueAt(idx) != isGranted;
+                permissionsForAccessory.setValueAt(idx, isGranted);
+            } else {
+                isChanged = true;
+                permissionsForAccessory.put(uid, isGranted);
+            }
+
+            if (isChanged) {
+                scheduleWritePermissionLocked();
+            }
+        }
+    }
+
+    private void readPermission(@NonNull XmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        int uid;
+        boolean isGranted;
+
+        try {
+            uid = XmlUtils.readIntAttribute(parser, "uid");
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "error reading usb permission uid", e);
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
+
+        // only use "true"/"false" as valid values
+        String isGrantedString = parser.getAttributeValue(null, "granted");
+        if (isGrantedString == null || !(isGrantedString.equals(Boolean.TRUE.toString())
+                || isGrantedString.equals(Boolean.FALSE.toString()))) {
+            Slog.e(TAG, "error reading usb permission granted state");
+            XmlUtils.skipCurrentTag(parser);
+            return;
+        }
+        isGranted = isGrantedString.equals(Boolean.TRUE.toString());
+        XmlUtils.nextElement(parser);
+        if ("usb-device".equals(parser.getName())) {
+            DeviceFilter filter = DeviceFilter.read(parser);
+            int idx = mDevicePersistentPermissionMap.indexOfKey(filter);
+            if (idx >= 0) {
+                SparseBooleanArray permissionsForDevice =
+                        mDevicePersistentPermissionMap.valueAt(idx);
+                permissionsForDevice.put(uid, isGranted);
+            } else {
+                SparseBooleanArray permissionsForDevice = new SparseBooleanArray();
+                mDevicePersistentPermissionMap.put(filter, permissionsForDevice);
+                permissionsForDevice.put(uid, isGranted);
+            }
+        } else if ("usb-accessory".equals(parser.getName())) {
+            AccessoryFilter filter = AccessoryFilter.read(parser);
+            int idx = mAccessoryPersistentPermissionMap.indexOfKey(filter);
+            if (idx >= 0) {
+                SparseBooleanArray permissionsForAccessory =
+                        mAccessoryPersistentPermissionMap.valueAt(idx);
+                permissionsForAccessory.put(uid, isGranted);
+            } else {
+                SparseBooleanArray permissionsForAccessory = new SparseBooleanArray();
+                mAccessoryPersistentPermissionMap.put(filter, permissionsForAccessory);
+                permissionsForAccessory.put(uid, isGranted);
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void readPermissionsLocked() {
+        mDevicePersistentPermissionMap.clear();
+        mAccessoryPersistentPermissionMap.clear();
+
+        try (FileInputStream in = mPermissionsFile.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            XmlUtils.nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if ("permission".equals(tagName)) {
+                    readPermission(parser);
+                } else {
+                    XmlUtils.nextElement(parser);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            if (DEBUG) Slog.d(TAG, "usb permissions file not found");
+        } catch (Exception e) {
+            Slog.e(TAG, "error reading usb permissions file, deleting to start fresh", e);
+            mPermissionsFile.delete();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void scheduleWritePermissionLocked() {
+        if (mIsCopyPermissionsScheduled) {
+            return;
+        }
+        mIsCopyPermissionsScheduled = true;
+
+        AsyncTask.execute(() -> {
+            int numDevices;
+            DeviceFilter[] devices;
+            int[][] uidsForDevices;
+            boolean[][] grantedValuesForDevices;
+
+            int numAccessories;
+            AccessoryFilter[] accessories;
+            int[][] uidsForAccessories;
+            boolean[][] grantedValuesForAccessories;
+
+            synchronized (mLock) {
+                // Copy the permission state so we can write outside of lock
+                numDevices = mDevicePersistentPermissionMap.size();
+                devices = new DeviceFilter[numDevices];
+                uidsForDevices = new int[numDevices][];
+                grantedValuesForDevices = new boolean[numDevices][];
+                for (int i = 0; i < numDevices; i++) {
+                    devices[i] = new DeviceFilter(mDevicePersistentPermissionMap.keyAt(i));
+                    SparseBooleanArray permissions = mDevicePersistentPermissionMap.valueAt(i);
+                    int numPermissions = permissions.size();
+                    uidsForDevices[i] = new int[numPermissions];
+                    grantedValuesForDevices[i] = new boolean[numPermissions];
+                    for (int j = 0; j < numPermissions; j++) {
+                        uidsForDevices[i][j] = permissions.keyAt(j);
+                        grantedValuesForDevices[i][j] = permissions.valueAt(j);
+                    }
+                }
+
+                numAccessories = mAccessoryPersistentPermissionMap.size();
+                accessories = new AccessoryFilter[numAccessories];
+                uidsForAccessories = new int[numAccessories][];
+                grantedValuesForAccessories = new boolean[numAccessories][];
+                for (int i = 0; i < numAccessories; i++) {
+                    accessories[i] =
+                            new AccessoryFilter(mAccessoryPersistentPermissionMap.keyAt(i));
+                    SparseBooleanArray permissions = mAccessoryPersistentPermissionMap.valueAt(i);
+                    int numPermissions = permissions.size();
+                    uidsForAccessories[i] = new int[numPermissions];
+                    grantedValuesForAccessories[i] = new boolean[numPermissions];
+                    for (int j = 0; j < numPermissions; j++) {
+                        uidsForAccessories[i][j] = permissions.keyAt(j);
+                        grantedValuesForAccessories[i][j] = permissions.valueAt(j);
+                    }
+                }
+                mIsCopyPermissionsScheduled = false;
+            }
+
+            synchronized (mPermissionsFile) {
+                FileOutputStream out = null;
+                try {
+                    out = mPermissionsFile.startWrite();
+                    FastXmlSerializer serializer = new FastXmlSerializer();
+                    serializer.setOutput(out, StandardCharsets.UTF_8.name());
+                    serializer.startDocument(null, true);
+                    serializer.startTag(null, "permissions");
+
+                    for (int i = 0; i < numDevices; i++) {
+                        int numPermissions = uidsForDevices[i].length;
+                        for (int j = 0; j < numPermissions; j++) {
+                            serializer.startTag(null, "permission");
+                            serializer.attribute(null, "uid",
+                                    Integer.toString(uidsForDevices[i][j]));
+                            serializer.attribute(null, "granted",
+                                    Boolean.toString(grantedValuesForDevices[i][j]));
+                            devices[i].write(serializer);
+                            serializer.endTag(null, "permission");
+                        }
+                    }
+
+                    for (int i = 0; i < numAccessories; i++) {
+                        int numPermissions = uidsForAccessories[i].length;
+                        for (int j = 0; j < numPermissions; j++) {
+                            serializer.startTag(null, "permission");
+                            serializer.attribute(null, "uid",
+                                    Integer.toString(uidsForAccessories[i][j]));
+                            serializer.attribute(null, "granted",
+                                    Boolean.toString(grantedValuesForDevices[i][j]));
+                            accessories[i].write(serializer);
+                            serializer.endTag(null, "permission");
+                        }
+                    }
+
+                    serializer.endTag(null, "permissions");
+                    serializer.endDocument();
+
+                    mPermissionsFile.finishWrite(out);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Failed to write permissions", e);
+                    if (out != null) {
+                        mPermissionsFile.failWrite(out);
+                    }
+                }
+            }
+        });
     }
 
     /**
@@ -222,7 +511,7 @@
         try {
             userContext.startActivityAsUser(intent, mUser);
         } catch (ActivityNotFoundException e) {
-            Slog.e(LOG_TAG, "unable to start UsbPermissionActivity");
+            Slog.e(TAG, "unable to start UsbPermissionActivity");
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -278,19 +567,19 @@
             ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
             // compare uid with packageName to foil apps pretending to be someone else
             if (aInfo.uid != uid) {
-                Slog.i(LOG_TAG, "Package " + packageName + " does not match caller's uid " + uid);
+                Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
                 return false;
             }
             targetSdkVersion = aInfo.targetSdkVersion;
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.i(LOG_TAG, "Package not found, likely due to invalid package name!");
+            Slog.i(TAG, "Package not found, likely due to invalid package name!");
             return false;
         }
 
         if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
             int allowed = mContext.checkCallingPermission(android.Manifest.permission.CAMERA);
             if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
-                Slog.i(LOG_TAG, "Camera permission required for USB video class devices");
+                Slog.i(TAG, "Camera permission required for USB video class devices");
                 return false;
             }
         }
@@ -342,7 +631,7 @@
             try {
                 pi.send(mContext, 0, intent);
             } catch (PendingIntent.CanceledException e) {
-                if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
             }
             return;
         }
@@ -353,7 +642,7 @@
                 try {
                     pi.send(mContext, 0, intent);
                 } catch (PendingIntent.CanceledException e) {
-                    if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                    if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
                 }
                 return;
             }
@@ -373,7 +662,7 @@
             try {
                 pi.send(mContext, 0, intent);
             } catch (PendingIntent.CanceledException e) {
-                if (DEBUG) Slog.d(LOG_TAG, "requestPermission PendingIntent was cancelled");
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
             }
             return;
         }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index de2dd10..3f2d8c8 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -41,6 +41,8 @@
                                  //     D4..0 Reserved, set to 0.
     private int mMaxPower;       // 8:1 Maximum Power Consumption in 2mA units
 
+    private boolean mBlockAudio; // leave it off for now. We be replace with a "Developer Option"
+
     private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
             new ArrayList<UsbInterfaceDescriptor>();
 
@@ -77,21 +79,35 @@
         mInterfaceDescriptors.add(interfaceDesc);
     }
 
+    private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) {
+        return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO
+                && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING;
+    }
+
     UsbConfiguration toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "  toAndroid()");
         }
+
+        // NOTE - This code running in the server process.
+        //TODO (pmclean@) - remove this
+//        int pid = android.os.Process.myPid();
+//        int uid = android.os.Process.myUid();
+//        Log.d(TAG, "  ---- pid:" + pid + " uid:" + uid);
+
         String name = parser.getDescriptorString(mConfigIndex);
         UsbConfiguration config = new
                 UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
-        UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
-        if (UsbDescriptorParser.DEBUG) {
-            Log.d(TAG, "    " + mInterfaceDescriptors.size() + " interfaces.");
+
+        ArrayList<UsbInterface> filteredInterfaces = new ArrayList<UsbInterface>();
+        for (UsbInterfaceDescriptor descriptor : mInterfaceDescriptors) {
+            if (!mBlockAudio || !isAudioInterface(descriptor)) {
+                filteredInterfaces.add(descriptor.toAndroid(parser));
+            }
         }
-        for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
-            interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
-        }
-        config.setInterfaces(interfaces);
+        UsbInterface[] interfaceArray = new UsbInterface[0];
+        interfaceArray = filteredInterfaces.toArray(interfaceArray);
+        config.setInterfaces(interfaceArray);
         return config;
     }
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 4f62d5a..6ffbd43 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -26,7 +26,7 @@
  */
 public final class UsbDescriptorParser {
     private static final String TAG = "UsbDescriptorParser";
-    public static final boolean DEBUG = false;
+    public static final boolean DEBUG = true;
 
     private final String mDeviceAddr;
 
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 5eb0a2f..4d0cfea 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -31,7 +31,7 @@
     public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
     public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
     public static final int DIRECTION_OUTPUT = 0x0000;
-    public static final int DIRECTION_INPUT = (byte) 0x0080;
+    public static final int DIRECTION_INPUT = 0x0080;
 
     public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
     public static final int TRANSTYPE_CONTROL = 0x00;
@@ -85,7 +85,7 @@
     }
 
     public int getEndpointAddress() {
-        return mEndpointAddress;
+        return mEndpointAddress & MASK_ENDPOINT_ADDRESS;
     }
 
     public int getAttributes() {
@@ -108,6 +108,10 @@
         return mSyncAddress;
     }
 
+    public int getDirection() {
+        return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
+    }
+
     /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
         if (UsbDescriptorParser.DEBUG) {
             Log.d(TAG, "toAndroid() type:"
@@ -137,11 +141,9 @@
 
         canvas.openList();
 
-        int address = getEndpointAddress();
         canvas.writeListItem("Address: "
-                + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
-                + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
-                == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
+                + ReportCanvas.getHexString(getEndpointAddress())
+                + (getDirection() == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
 
         int attributes = getAttributes();
         canvas.openListItem();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 1dc6069..64dbd97 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -31,7 +31,6 @@
  */
 public class UsbInterfaceDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbInterfaceDescriptor";
-
     protected int mInterfaceNumber;   // 2:1 Number of Interface
     protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
     protected byte mNumEndpoints;     // 4:1 Number of Endpoints used for this interface
@@ -73,6 +72,19 @@
         return mNumEndpoints;
     }
 
+    /**
+     * @param index Index of desired UsbEndpointDescriptor.
+     * @return the UsbEndpointDescriptor descriptor at the specified index, or
+     *  null if an invalid index.
+     */
+    public UsbEndpointDescriptor getEndpointDescriptor(int index) {
+        if (index < 0 || index >= mEndpointDescriptors.size()) {
+            return null;
+        }
+
+        return mEndpointDescriptors.get(index);
+    }
+
     public int getUsbClass() {
         return mUsbClass;
     }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 9933756..735b9a1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -325,9 +325,8 @@
                     false /* Don't notify for synchronous calls */);
 
                                 // Initialize power save, call active state monitoring logic.
-            if (status == STATUS_OK && !mRecognitionRequested) {
+            if (status == STATUS_OK) {
                 initializeTelephonyAndPowerStateListeners();
-                mRecognitionRequested = true;
             }
 
             return status;
@@ -481,6 +480,9 @@
 
         if (unloadModel && modelData.isModelLoaded()) {
             Slog.d(TAG, "Unloading previously loaded stale model.");
+            if (mModule == null) {
+                return STATUS_ERROR;
+            }
             status = mModule.unloadSoundModel(modelData.getHandle());
             MetricsLogger.count(mContext, "sth_unloading_stale_model", 1);
             if (status != SoundTrigger.STATUS_OK) {
@@ -550,6 +552,9 @@
                 }
             }
 
+            if (mModule == null) {
+                return STATUS_ERROR;
+            }
             int status = mModule.unloadSoundModel(modelData.getHandle());
             if (status != SoundTrigger.STATUS_OK) {
                 Slog.w(TAG, "unloadGenericSoundModel() call failed with " + status);
@@ -878,6 +883,7 @@
             mContext.unregisterReceiver(mPowerSaveModeListener);
             mPowerSaveModeListener = null;
         }
+        mRecognitionRequested = false;
     }
 
     // Clears state for all models (generic and keyphrase).
@@ -924,6 +930,9 @@
     }
 
     private void initializeTelephonyAndPowerStateListeners() {
+        if (mRecognitionRequested) {
+            return;
+        }
         long token = Binder.clearCallingIdentity();
         try {
             // Get the current call state synchronously for the first recognition.
@@ -941,6 +950,8 @@
             }
             mIsPowerSaveMode = mPowerManager.getPowerSaveState(ServiceType.SOUND)
                     .batterySaverEnabled;
+
+            mRecognitionRequested = true;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -987,6 +998,9 @@
         if (exception != null) {
           Slog.e(TAG, "forceStopAndUnloadModel", exception);
         }
+        if (mModule == null) {
+            return;
+        }
         if (modelData.isModelStarted()) {
             Slog.d(TAG, "Stopping previously started dangling model " + modelData.getHandle());
             if (mModule.stopRecognition(modelData.getHandle()) != STATUS_OK) {
@@ -1093,6 +1107,13 @@
     // a recognition include: no active phone call or not being in a power save mode. Also,
     // the native service should be enabled.
     private boolean isRecognitionAllowed() {
+        // if mRecognitionRequested is false, call and power state listeners are not registered so
+        // we read current state directly from services
+        if (!mRecognitionRequested) {
+            mCallActive = mTelephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
+            mIsPowerSaveMode =
+                mPowerManager.getPowerSaveState(ServiceType.SOUND).batterySaverEnabled;
+        }
         return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
     }
 
@@ -1116,6 +1137,9 @@
             return STATUS_OK;
         }
 
+        if (mModule == null) {
+            return STATUS_ERROR;
+        }
         int status = mModule.startRecognition(handle, config);
         if (status != SoundTrigger.STATUS_OK) {
             Slog.w(TAG, "startRecognition failed with " + status);
@@ -1152,8 +1176,11 @@
     }
 
     private int stopRecognitionLocked(ModelData modelData, boolean notify) {
-        IRecognitionStatusCallback callback = modelData.getCallback();
+        if (mModule == null) {
+            return STATUS_ERROR;
+        }
 
+        IRecognitionStatusCallback callback = modelData.getCallback();
         // Stop recognition.
         int status = STATUS_OK;
 
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index a8cafb3..1dd3972 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -666,6 +666,23 @@
                 return ret;
             }
         }
+
+        @Override
+        @Nullable
+        public ModuleProperties getModuleProperties() {
+            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
+            if (!isInitialized()) return null;
+            if (DEBUG) {
+                Slog.i(TAG, "getModuleProperties()");
+            }
+
+            synchronized (mLock) {
+                ModuleProperties properties = mSoundTriggerHelper.getModuleProperties();
+                sEventLogger.log(new SoundTriggerLogger.StringEvent(
+                        "getModuleProperties(): " + properties.toString()));
+                return properties;
+            }
+        }
     }
 
     /**
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index f16dec6..0ff9273 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -194,7 +194,7 @@
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
                         Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY
-                                | Context.BIND_FOREGROUND_SERVICE
+                                | Context.BIND_SCHEDULE_LIKE_TOP_APP
                                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                         new UserHandle(mUser));
             }
diff --git a/services/wifi/java/android/net/wifi/WifiStackClient.java b/services/wifi/java/android/net/wifi/WifiStackClient.java
index fa66e4c..64af7a8 100644
--- a/services/wifi/java/android/net/wifi/WifiStackClient.java
+++ b/services/wifi/java/android/net/wifi/WifiStackClient.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityModuleConnector;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -56,13 +57,20 @@
         public void onModuleServiceConnected(IBinder service) {
             Log.i(TAG, "Wifi stack connected");
 
-            registerWifiStackService(service);
-            IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
-            registerApiServiceAndStart(connector, Context.WIFI_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE);
-            registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE);
+            // spin up a new thread to not block system_server main thread
+            HandlerThread thread = new HandlerThread("InitWifiServicesThread");
+            thread.start();
+            thread.getThreadHandler().post(() -> {
+                registerWifiStackService(service);
+                IWifiStackConnector connector = IWifiStackConnector.Stub.asInterface(service);
+                registerApiServiceAndStart(connector, Context.WIFI_SCANNING_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_P2P_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_AWARE_SERVICE);
+                registerApiServiceAndStart(connector, Context.WIFI_RTT_RANGING_SERVICE);
+
+                thread.quitSafely();
+            });
         }
     }
 
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a4906d7..a806320 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,11 +17,13 @@
 android_app {
     name: "startop_test_app",
     srcs: [
+        "src/CPUIntensive.java",
         "src/EmptyActivity.java",
         "src/LayoutInflationActivity.java",
         "src/ComplexLayoutInflationActivity.java",
         "src/FrameLayoutInflationActivity.java",
+        "src/SystemServerBenchmarkActivity.java",
         "src/TextViewInflationActivity.java",
     ],
-    platform_apis: true,
+    sdk_version: "26", // Android O (8.0) and higher
 }
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 467d8f7..15785d4 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -71,6 +71,19 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity
+            android:label="SystemServer Benchmark"
+            android:name=".SystemServerBenchmarkActivity"
+            android:exported="true" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
     </application>
 
 </manifest>
diff --git a/startop/apps/test/res/layout/system_server_benchmark_page.xml b/startop/apps/test/res/layout/system_server_benchmark_page.xml
new file mode 100644
index 0000000..337fe65
--- /dev/null
+++ b/startop/apps/test/res/layout/system_server_benchmark_page.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <GridLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:columnCount="3"
+        android:id="@+id/benchmark_list">
+
+    <TextView android:text="Benchmark"/>
+    <TextView android:text="Mean (ms)"/>
+    <TextView android:text="Stdev (ms)"/>
+
+    </GridLayout>
+</ScrollView>
diff --git a/startop/apps/test/src/CPUIntensive.java b/startop/apps/test/src/CPUIntensive.java
new file mode 100644
index 0000000..a411e8c
--- /dev/null
+++ b/startop/apps/test/src/CPUIntensive.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ *  A threaded CPU intensive class for use in benchmarks.
+ */
+
+package com.android.startop.test;
+
+final class CPUIntensive {
+    public static final int THREAD_COUNT = 8;
+    public static final int ARRAY_SIZE = 30000;
+    public static int[][] array = new int[THREAD_COUNT][ARRAY_SIZE];
+
+    static class WorkerThread extends Thread {
+        int mThreadNumber;
+        WorkerThread(int number) {
+            mThreadNumber = number;
+        }
+        public void run() {
+            final int arrayLength = array[mThreadNumber].length;
+            for (int i = 0; i < arrayLength; ++i) {
+                array[mThreadNumber][i] = i * i;
+            }
+            for (int i = 0; i < arrayLength; ++i) {
+                for (int j = 0; j < arrayLength; ++j) {
+                    int swap = array[mThreadNumber][j];
+                    array[mThreadNumber][j] = array[mThreadNumber][(j + i) % arrayLength];
+                    array[mThreadNumber][(j + i) % arrayLength] = swap;
+                }
+            }
+        }
+    };
+
+    public static void doSomeWork(int threadCount) {
+        WorkerThread[] threads = new WorkerThread[threadCount];
+        // Create the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i] = new WorkerThread(i);
+        }
+        // Start the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            threads[i].start();
+        }
+        // Join the threads.
+        for (int i = 0; i < threadCount; ++i) {
+            try {
+                threads[i].join();
+            } catch (Exception ex) {
+            }
+        }
+    }
+}
+
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..c8d9fde
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.startop.test;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+
+class Benchmark {
+    // Time limit to run benchmarks in seconds
+    public static final int TIME_LIMIT = 5;
+
+    public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
+        Context context = parent.getContext();
+        Button button = new Button(context);
+        TextView mean = new TextView(context);
+        TextView stdev = new TextView(context);
+
+        button.setText(name);
+        mean.setText("");
+        stdev.setText("");
+
+        button.setOnClickListener((_button) -> {
+            mean.setText("Running...");
+            stdev.setText("");
+
+            new AsyncTask() {
+                double resultMean = 0;
+                double resultStdev = 0;
+
+                @Override
+                protected Object doInBackground(Object... _args) {
+                    long startTime = System.nanoTime();
+                    int count = 0;
+
+                    // Run benchmark
+                    while (true) {
+                        long elapsed = -System.nanoTime();
+                        thunk.run();
+                        elapsed += System.nanoTime();
+
+                        count++;
+                        double elapsedVariance = (double) elapsed - resultMean;
+                        resultMean += elapsedVariance / count;
+                        resultStdev += elapsedVariance * ((double) elapsed - resultMean);
+
+                        if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
+                            break;
+                        }
+                    }
+                    resultStdev = Math.sqrt(resultStdev / (count - 1));
+
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Object _result) {
+                    mean.setText(String.format("%.3f", resultMean / 1e6));
+                    stdev.setText(String.format("%.3f", resultStdev / 1e6));
+                }
+            }.execute(new Object());
+        });
+
+        parent.addView(button);
+        parent.addView(mean);
+        parent.addView(stdev);
+    }
+}
+
+public class SystemServerBenchmarkActivity extends Activity {
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.system_server_benchmark_page);
+
+        GridLayout benchmarkList = findViewById(R.id.benchmark_list);
+
+        new Benchmark(benchmarkList, "Empty", () -> {
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> {
+            CPUIntensive.doSomeWork(1);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> {
+            CPUIntensive.doSomeWork(2);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> {
+            CPUIntensive.doSomeWork(4);
+        });
+
+        new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> {
+            CPUIntensive.doSomeWork(8);
+        });
+
+        PackageManager pm = getPackageManager();
+        new Benchmark(benchmarkList, "getInstalledApplications", () -> {
+            pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+        });
+
+        new Benchmark(benchmarkList, "getInstalledPackages", () -> {
+            pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
+        });
+
+        new Benchmark(benchmarkList, "getPackageInfo", () -> {
+            try {
+                pm.getPackageInfo("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "getApplicationInfo", () -> {
+            try {
+                pm.getApplicationInfo("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        try {
+            ApplicationInfo app = pm.getApplicationInfo("com.android.startop.test", 0);
+            new Benchmark(benchmarkList, "getResourcesForApplication", () -> {
+                try {
+                    pm.getResourcesForApplication(app);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            });
+
+            new Benchmark(benchmarkList, "getPackagesForUid", () -> {
+                pm.getPackagesForUid(app.uid);
+            });
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+
+        ComponentName component = new ComponentName(this, this.getClass());
+        new Benchmark(benchmarkList, "getActivityInfo", () -> {
+            try {
+                pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
+            pm.getLaunchIntentForPackage("com.android.startop.test");
+        });
+
+        new Benchmark(benchmarkList, "getPackageUid", () -> {
+            try {
+                pm.getPackageUid("com.android.startop.test", 0);
+            } catch (NameNotFoundException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        new Benchmark(benchmarkList, "checkPermission", () -> {
+            // Check for the first permission I could find.
+            pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
+        });
+
+        new Benchmark(benchmarkList, "checkSignatures", () -> {
+            // Compare with settings, since settings is on both AOSP and Master builds
+            pm.checkSignatures("com.android.settings", "com.android.startop.test");
+        });
+
+        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+        new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
+            pm.queryBroadcastReceivers(intent, 0);
+        });
+
+        new Benchmark(benchmarkList, "hasSystemFeature", () -> {
+            pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+        });
+
+        new Benchmark(benchmarkList, "resolveService", () -> {
+            pm.resolveService(intent, 0);
+        });
+
+        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+        new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
+            am.getRunningAppProcesses();
+        });
+
+    }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 31bd341..902da4c 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -275,7 +275,8 @@
             Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
             return false;
         }
-        invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+        invokeRemote(mIorapRemote,
+            (IIorap remote) -> remote.setTaskListener(new RemoteTaskListener()) );
         registerInProcessListenersLocked();
 
         return true;
@@ -323,8 +324,9 @@
                         mSequenceId, intent));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.IntentStarted(mSequenceId, intent))
             );
         }
@@ -335,8 +337,9 @@
                 Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.IntentFailed(mSequenceId))
             );
         }
@@ -349,8 +352,9 @@
                         mSequenceId, activity, temperature));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                             new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature))
             );
         }
@@ -362,8 +366,9 @@
                         mSequenceId, activity));
             }
 
-            invokeRemote(() ->
-                    mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                             new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId,
                                     activity)));
         }
@@ -375,8 +380,9 @@
                         mSequenceId, activity));
             }
 
-            invokeRemote(() ->
-                mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+            invokeRemote(mIorapRemote,
+                (IIorap remote) ->
+                    remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
                         new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
             );
         }
@@ -501,8 +507,8 @@
                 mRunningJobs.put(request, params);
             }
 
-            if (!invokeRemote( () ->
-                    mIorapRemote.onJobScheduledEvent(request,
+            if (!invokeRemote(mIorapRemote, (IIorap remote) ->
+                    remote.onJobScheduledEvent(request,
                             JobScheduledEvent.createIdleMaintenance(
                                     JobScheduledEvent.TYPE_START_JOB,
                                     params))
@@ -539,8 +545,8 @@
 
             // Notify iorapd to stop (abort) the job.
             if (wasTracking) {
-                invokeRemote(() ->
-                        mIorapRemote.onJobScheduledEvent(RequestId.nextValueForSequence(),
+                invokeRemote(mIorapRemote, (IIorap remote) ->
+                        remote.onJobScheduledEvent(RequestId.nextValueForSequence(),
                                 JobScheduledEvent.createIdleMaintenance(
                                         JobScheduledEvent.TYPE_STOP_JOB,
                                         params))
@@ -632,12 +638,17 @@
     /** Allow passing lambdas to #invokeRemote */
     private interface RemoteRunnable {
         // TODO: run(RequestId) ?
-        void run() throws RemoteException;
+        void run(IIorap iorap) throws RemoteException;
     }
 
-    private static boolean invokeRemote(RemoteRunnable r) {
+    // Always pass in the iorap directly here to avoid data race.
+    private static boolean invokeRemote(IIorap iorap, RemoteRunnable r) {
+       if (iorap == null) {
+         Log.w(TAG, "IIorap went to null in this thread, drop invokeRemote.");
+         return false;
+       }
        try {
-           r.run();
+           r.run(iorap);
            return true;
        } catch (RemoteException e) {
            // This could be a logic error (remote side returning error), which we need to fix.
diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py
index eb582f9..fa1c4e6 100755
--- a/startop/scripts/app_startup/app_startup_runner.py
+++ b/startop/scripts/app_startup/app_startup_runner.py
@@ -41,11 +41,12 @@
 sys.path.append(os.path.dirname(DIR))
 import lib.cmd_utils as cmd_utils
 import lib.print_utils as print_utils
-import iorap.compiler as compiler
 from app_startup.run_app_with_prefetch import PrefetchAppRunner
 import app_startup.lib.args_utils as args_utils
 from app_startup.lib.data_frame import DataFrame
 from app_startup.lib.perfetto_trace_collector import PerfettoTraceCollector
+from iorap.compiler import CompilerType
+import iorap.compiler as compiler
 
 # The following command line options participate in the combinatorial generation.
 # All other arguments have a global effect.
@@ -58,8 +59,6 @@
 
 CollectorPackageInfo = NamedTuple('CollectorPackageInfo',
                                   [('package', str), ('compiler_filter', str)])
-_COMPILER_SCRIPT = os.path.join(os.path.dirname(os.path.dirname(
-    os.path.realpath(__file__))), 'iorap/compiler.py')
 # by 2; systrace starts up slowly.
 
 _UNLOCK_SCREEN_SCRIPT = os.path.join(
@@ -135,6 +134,10 @@
                               action='append',
                               help='The trace duration (milliseconds) in '
                                    'compilation')
+  optional_named.add_argument('--compiler-type', dest='compiler_type',
+                              type=CompilerType, choices=list(CompilerType),
+                              default=CompilerType.DEVICE,
+                              help='The type of compiler.')
 
   return parser.parse_args(argv)
 
@@ -211,26 +214,26 @@
 
   return DataFrame(d)
 
-def compile_perfetto_trace(inodes_path: str,
+def build_ri_compiler_argv(inodes_path: str,
                            perfetto_trace_file: str,
-                           trace_duration: Optional[timedelta]) -> TextIO:
-  compiler_trace_file = tempfile.NamedTemporaryFile()
-  argv = [_COMPILER_SCRIPT, '-i', inodes_path, '--perfetto-trace',
-          perfetto_trace_file, '-o', compiler_trace_file.name]
+                           trace_duration: Optional[timedelta]
+                           ) -> str:
+  argv = ['-i', inodes_path, '--perfetto-trace',
+          perfetto_trace_file]
 
   if trace_duration is not None:
     argv += ['--duration', str(int(trace_duration.total_seconds()
-                               * PerfettoTraceCollector.MS_PER_SEC))]
+                                   * PerfettoTraceCollector.MS_PER_SEC))]
 
   print_utils.debug_print(argv)
-  compiler.main(argv)
-  return compiler_trace_file
+  return argv
 
 def execute_run_using_perfetto_trace(collector_info,
                                      run_combos: Iterable[RunCommandArgs],
                                      simulate: bool,
                                      inodes_path: str,
-                                     timeout: int) -> DataFrame:
+                                     timeout: int,
+                                     compiler_type: CompilerType) -> DataFrame:
   """ Executes run based on perfetto trace. """
   passed, perfetto_trace_file = run_perfetto_collector(collector_info,
                                                        timeout,
@@ -244,9 +247,15 @@
         if simulate:
           compiler_trace_file = tempfile.NamedTemporaryFile()
         else:
-          compiler_trace_file = compile_perfetto_trace(inodes_path,
-                                                       perfetto_trace_file.name,
-                                                       combos.trace_duration)
+          ri_compiler_argv = build_ri_compiler_argv(inodes_path,
+                                                    perfetto_trace_file.name,
+                                                    combos.trace_duration)
+          compiler_trace_file = compiler.compile(compiler_type,
+                                                 inodes_path,
+                                                 ri_compiler_argv,
+                                                 combos.package,
+                                                 combos.activity)
+
         with compiler_trace_file:
           combos = combos._replace(input=compiler_trace_file.name)
           print_utils.debug_print(combos)
@@ -261,7 +270,8 @@
     grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]],
     simulate: bool,
     inodes_path: str,
-    timeout: int):
+    timeout: int,
+    compiler_type: CompilerType):
   # nothing will work if the screen isn't unlocked first.
   cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT],
                                       timeout,
@@ -273,7 +283,8 @@
                                                 run_combos,
                                                 simulate,
                                                 inodes_path,
-                                                timeout)
+                                                timeout,
+                                                compiler_type)
 
 def gather_results(commands: Iterable[Tuple[DataFrame]],
                    key_list: List[str], value_list: List[Tuple[str, ...]]):
@@ -361,7 +372,8 @@
   exec = execute_run_combos(grouped_combos(),
                             opts.simulate,
                             opts.inodes,
-                            opts.timeout)
+                            opts.timeout,
+                            opts.compiler_type)
 
   results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos())
 
diff --git a/startop/scripts/app_startup/app_startup_runner_test.py b/startop/scripts/app_startup/app_startup_runner_test.py
index 42ea5f0..382f6f3 100755
--- a/startop/scripts/app_startup/app_startup_runner_test.py
+++ b/startop/scripts/app_startup/app_startup_runner_test.py
@@ -92,7 +92,7 @@
   """
   d = {'compiler_filters': None, 'simulate': False, 'debug': False,
        'output': None, 'timeout': 10, 'loop_count': 1, 'inodes': None,
-       'trace_duration': None}
+       'trace_duration': None, 'compiler_type': asr.CompilerType.HOST}
   d.update(kwargs)
   return d
 
diff --git a/startop/scripts/app_startup/lib/adb_utils.py b/startop/scripts/app_startup/lib/adb_utils.py
index e56a968..3cebc9a 100644
--- a/startop/scripts/app_startup/lib/adb_utils.py
+++ b/startop/scripts/app_startup/lib/adb_utils.py
@@ -42,8 +42,8 @@
 def vm_drop_cache():
   """Free pagecache and slab object."""
   cmd_utils.run_adb_shell_command('echo 3 > /proc/sys/vm/drop_caches')
-  # Sleep a little bit to provide enougth time for cache cleanup.
-  time.sleep(2)
+  # Sleep a little bit to provide enough time for cache cleanup.
+  time.sleep(1)
 
 def root():
   """Roots adb and successive adb commands will run under root."""
diff --git a/startop/scripts/iorap/compiler.py b/startop/scripts/iorap/compiler.py
old mode 100755
new mode 100644
index c940fe9..1426d34
--- a/startop/scripts/iorap/compiler.py
+++ b/startop/scripts/iorap/compiler.py
@@ -1,322 +1,73 @@
 #!/usr/bin/env python3
-
 #
-# Copyright (C) 2019 The Android Open Source Project
+# Copyright 2019, The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#      http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-#
 
-#
-# Dependencies:
-#
-# $> sudo apt-get install python3-pip
-# $> pip3 install --user protobuf sqlalchemy sqlite3
-#
-
-import optparse
+import importlib
 import os
-import re
 import sys
 import tempfile
-from pathlib import Path
-from datetime import timedelta
-from typing import Iterable, Optional, List
+from enum import Enum
+from typing import TextIO, List
 
+# local import
 DIR = os.path.abspath(os.path.dirname(__file__))
 sys.path.append(os.path.dirname(DIR))
-from iorap.generated.TraceFile_pb2 import *
-from iorap.lib.inode2filename import Inode2Filename
+import lib.print_utils as print_utils
 
-parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-sys.path.append(parent_dir_name)
-from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
-    RawFtraceEntry
-import lib.cmd_utils as cmd_utils
+# Type of compiler.
+class CompilerType(Enum):
+  HOST = 1  # iorap.cmd.compiler on host
+  DEVICE = 2  # adb shell iorap.cmd.compiler
+  RI = 3  # compiler.py
 
-_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
-ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
-TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
-    'external/perfetto/tools/traceconv')
+def compile_perfetto_trace_ri(
+    argv: List[str],
+    compiler) -> TextIO:
+  print_utils.debug_print('Compile using RI compiler.')
+  compiler_trace_file = tempfile.NamedTemporaryFile()
+  argv.extend(['-o', compiler_trace_file.name])
+  print_utils.debug_print(argv)
+  compiler.main([''] + argv)
+  return compiler_trace_file
 
-class PageRun:
-  """
-  Intermediate representation for a run of one or more pages.
-  """
-  def __init__(self, device_number: int, inode: int, offset: int, length: int):
-    self.device_number = device_number
-    self.inode = inode
-    self.offset = offset
-    self.length = length
+def compile_perfetto_trace_device(inodes_path: str,
+                                  package: str,
+                                  activity: str,
+                                  compiler) -> TextIO:
+  print_utils.debug_print('Compile using on-device compiler.')
+  compiler_trace_file = tempfile.NamedTemporaryFile()
+  compiler.main(inodes_path, package, activity, compiler_trace_file.name)
+  return compiler_trace_file
 
-  def __str__(self):
-    return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
-        %(self.device_number, self.inode, self.offset, self.length)
+def compile(compiler_type: CompilerType,
+            inodes_path: str,
+            ri_compiler_argv,
+            package: str,
+            activity: str) -> TextIO:
+  if compiler_type == CompilerType.RI:
+    compiler = importlib.import_module('iorap.compiler_ri')
+    compiler_trace_file = compile_perfetto_trace_ri(ri_compiler_argv,
+                                                    compiler)
+    return compiler_trace_file
+  if compiler_type == CompilerType.DEVICE:
+    compiler = importlib.import_module('iorap.compiler_device')
+    compiler_trace_file = compile_perfetto_trace_device(inodes_path,
+                                                        package,
+                                                        activity,
+                                                        compiler)
+    return compiler_trace_file
 
-def debug_print(msg):
-  #print(msg)
-  pass
-
-UNDER_LAUNCH = False
-
-def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
-  global _PAGE_SIZE
-
-  runs = [
-      PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
-              length=_PAGE_SIZE)
-        for pg_entry in page_cache_entries
-  ]
-
-  for r in runs:
-    debug_print(r)
-
-  print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
-
-  return runs
-
-def optimize_page_runs(page_runs):
-  new_entries = []
-  last_entry = None
-  for pg_entry in page_runs:
-    if last_entry:
-      if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
-        # we are dealing with a run for the same exact file as a previous run.
-        if pg_entry.offset == last_entry.offset + last_entry.length:
-          # trivially contiguous entries. merge them together.
-          last_entry.length += pg_entry.length
-          continue
-    # Default: Add the run without merging it to a previous run.
-    last_entry = pg_entry
-    new_entries.append(pg_entry)
-  return new_entries
-
-def is_filename_matching_filter(file_name, filters=[]):
-  """
-  Blacklist-style regular expression filters.
-
-  :return: True iff file_name has an RE match in one of the filters.
-  """
-  for filt in filters:
-    res = re.search(filt, file_name)
-    if res:
-      return True
-
-  return False
-
-def build_protobuf(page_runs, inode2filename, filters=[]):
-  trace_file = TraceFile()
-  trace_file_index = trace_file.index
-
-  file_id_counter = 0
-  file_id_map = {} # filename -> id
-
-  stats_length_total = 0
-  filename_stats = {} # filename -> total size
-
-  skipped_inode_map = {}
-  filtered_entry_map = {} # filename -> count
-
-  for pg_entry in page_runs:
-    fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
-    if not fn:
-      skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
-      continue
-
-    filename = fn
-
-    if filters and not is_filename_matching_filter(filename, filters):
-      filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
-      continue
-
-    file_id = file_id_map.get(filename)
-    if not file_id:
-      file_id = file_id_counter
-      file_id_map[filename] = file_id_counter
-      file_id_counter = file_id_counter + 1
-
-      file_index_entry = trace_file_index.entries.add()
-      file_index_entry.id = file_id
-      file_index_entry.file_name = filename
-
-    # already in the file index, add the file entry.
-    file_entry = trace_file.list.entries.add()
-    file_entry.index_id = file_id
-    file_entry.file_length = pg_entry.length
-    stats_length_total += file_entry.file_length
-    file_entry.file_offset = pg_entry.offset
-
-    filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
-
-  for inode, count in skipped_inode_map.items():
-    print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
-
-  print("Stats: Sum of lengths %d" %(stats_length_total))
-
-  if filters:
-    print("Filter: %d total files removed." %(len(filtered_entry_map)))
-
-    for fn, count in filtered_entry_map.items():
-      print("Filter: File '%s' removed '%d' entries." %(fn, count))
-
-  for filename, file_size in filename_stats.items():
-    print("%s,%s" %(filename, file_size))
-
-  return trace_file
-
-def calc_trace_end_time(trace2db: Trace2Db,
-                        trace_duration: Optional[timedelta]) -> float:
-  """
-  Calculates the end time based on the trace duration.
-  The start time is the first receiving mm file map event.
-  The end time is the start time plus the trace duration.
-  All of them are in milliseconds.
-  """
-  # If the duration is not set, assume all time is acceptable.
-  if trace_duration is None:
-    # float('inf')
-    return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
-
-  first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
-      MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
-      RawFtraceEntry.timestamp).first()
-
-  return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
-
-def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
-  end_time = calc_trace_end_time(trace2db, trace_duration)
-  # SELECT * FROM tbl ORDER BY id;
-  return trace2db.session.query(MmFilemapAddToPageCache).join(
-      MmFilemapAddToPageCache.raw_ftrace_entry).filter(
-      RawFtraceEntry.timestamp <= end_time).order_by(
-      MmFilemapAddToPageCache.id).all()
-
-def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
-                                         path_to_tmp_systrace: str) -> None:
-  """ Transforms the systrace file from perfetto trace. """
-  cmd_utils.run_command_nofail([str(TRACECONV_BIN),
-                                'systrace',
-                                path_to_perfetto_trace,
-                                path_to_tmp_systrace])
-
-
-def run(sql_db_path:str,
-        trace_file:str,
-        trace_duration:Optional[timedelta],
-        output_file:str,
-        inode_table:str,
-        filter:List[str]) -> int:
-  trace2db = Trace2Db(sql_db_path)
-  # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
-  trace2db.set_raw_ftrace_entry_filter(\
-      lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
-  # TODO: parse multiple trace files here.
-  parse_count = trace2db.parse_file_into_db(trace_file)
-
-  mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
-                                                              trace_duration)
-  print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
-
-  page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
-  print("DONE. Converted %d entries" %(len(page_runs)))
-
-  # TODO: flags to select optimizations.
-  optimized_page_runs = optimize_page_runs(page_runs)
-  print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
-
-  print("Build protobuf...")
-  trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
-
-  print("Write protobuf to file...")
-  output_file = open(output_file, 'wb')
-  output_file.write(trace_file.SerializeToString())
-  output_file.close()
-
-  print("DONE")
-
-  # TODO: Silent running mode [no output except on error] for build runs.
-
-  return 0
-
-def main(argv):
-  parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
-  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
-                    help='Read cached inode data from a file saved earlier with pagecache.py -d')
-  parser.add_option('-t', dest='trace_file', metavar='FILE',
-                    help='Path to systrace file (trace.html) that will be parsed')
-  parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
-                    metavar='FILE',
-                    help='Path to perfetto trace that will be parsed')
-
-  parser.add_option('--db', dest='sql_db', metavar='FILE',
-                    help='Path to intermediate sqlite3 database [default: in-memory].')
-
-  parser.add_option('-f', dest='filter', action="append", default=[],
-                    help="Add file filter. All file entries not matching one of the filters are discarded.")
-
-  parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
-                    help="Exclude all events not inside launch_lock")
-
-  parser.add_option('-o', dest='output_file', metavar='FILE',
-                    help='Output protobuf file')
-
-  parser.add_option('--duration', dest='trace_duration', action="store",
-                    type=int, help='The duration of trace in milliseconds.')
-
-  options, categories = parser.parse_args(argv[1:])
-
-  # TODO: OptionParser should have some flags to make these mandatory.
-  if not options.inode_data_file:
-    parser.error("-i is required")
-  if not options.trace_file and not options.perfetto_trace_file:
-    parser.error("one of -t or --perfetto-trace is required")
-  if options.trace_file and options.perfetto_trace_file:
-    parser.error("please enter either -t or --perfetto-trace, not both")
-  if not options.output_file:
-    parser.error("-o is required")
-
-  if options.launch_lock:
-    print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
-
-  inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
-
-  sql_db_path = ":memory:"
-  if options.sql_db:
-    sql_db_path = options.sql_db
-
-  trace_duration = timedelta(milliseconds=options.trace_duration) if \
-    options.trace_duration is not None else None
-
-  # if the input is systrace
-  if options.trace_file:
-    return run(sql_db_path,
-               options.trace_file,
-               trace_duration,
-               options.output_file,
-               inode_table,
-               options.filter)
-
-  # if the input is perfetto trace
-  # TODO python 3.7 switch to using nullcontext
-  with tempfile.NamedTemporaryFile() as trace_file:
-    transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
-                                         trace_file.name)
-    return run(sql_db_path,
-               trace_file.name,
-               trace_duration,
-               options.output_file,
-               inode_table,
-               options.filter)
-
-if __name__ == '__main__':
-  print(sys.argv)
-  sys.exit(main(sys.argv))
+  # Should not arrive here.
+  raise ValueError('Unknown compiler type')
diff --git a/startop/scripts/iorap/compiler_device.py b/startop/scripts/iorap/compiler_device.py
new file mode 100644
index 0000000..d941cd9
--- /dev/null
+++ b/startop/scripts/iorap/compiler_device.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import sys
+from typing import List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))  # framework/base/startop/script
+import lib.print_utils as print_utils
+import iorap.lib.iorapd_utils as iorapd_utils
+from app_startup.lib.app_runner import AppRunner
+
+IORAP_COMMON_BASH_SCRIPT = os.path.join(DIR, 'common')
+
+def parse_options(argv: List[str] = None):
+  """Parses command line arguments and returns an argparse Namespace object."""
+  parser = argparse.ArgumentParser(description="Compile perfetto trace file")
+  required_named = parser.add_argument_group('required named arguments')
+
+  required_named.add_argument('-i', dest='inodes', metavar='FILE',
+                              help='Read cached inode data from a file saved '
+                                   'earlier with pagecache.py -d')
+  required_named.add_argument('-p', dest='package',
+                              help='Package of the app to be compiled')
+
+  optional_named = parser.add_argument_group('optional named arguments')
+  optional_named.add_argument('-o', dest='output',
+                              help='The compiled trace is stored into the output file')
+  optional_named.add_argument('-a', dest='activity',
+                              help='Activity of the app to be compiled')
+  optional_named.add_argument('-d', dest='debug', action='store_true'
+                              , help='Activity of the app to be compiled')
+
+  return parser.parse_args(argv)
+
+def main(inodes, package, activity, output, **kwargs) -> int:
+  """Entries of the program."""
+  if not activity:
+    activity = AppRunner.get_activity(package)
+
+  passed = iorapd_utils.compile_perfetto_trace_on_device(package, activity,
+                                                         inodes)
+  if passed and output:
+    iorapd_utils.get_iorapd_compiler_trace(package, activity, output)
+
+  return 0
+
+if __name__ == '__main__':
+  opts = parse_options()
+  if opts.debug:
+    print_utils.DEBUG = opts.debug
+  print_utils.debug_print(opts)
+  sys.exit(main(**(vars(opts))))
diff --git a/startop/scripts/iorap/compiler_ri.py b/startop/scripts/iorap/compiler_ri.py
new file mode 100755
index 0000000..90fc8a8
--- /dev/null
+++ b/startop/scripts/iorap/compiler_ri.py
@@ -0,0 +1,325 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Dependencies:
+#
+# $> sudo apt-get install python3-pip
+# $> pip3 install --user protobuf sqlalchemy sqlite3
+#
+
+import optparse
+import os
+import re
+import sys
+import tempfile
+from pathlib import Path
+from datetime import timedelta
+from typing import Iterable, Optional, List
+
+DIR = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(os.path.dirname(DIR))
+from iorap.generated.TraceFile_pb2 import *
+from iorap.lib.inode2filename import Inode2Filename
+
+parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+sys.path.append(parent_dir_name)
+from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \
+    RawFtraceEntry
+import lib.cmd_utils as cmd_utils
+
+_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes.
+ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3]
+TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath(
+    'external/perfetto/tools/traceconv')
+
+class PageRun:
+  """
+  Intermediate representation for a run of one or more pages.
+  """
+  def __init__(self, device_number: int, inode: int, offset: int, length: int):
+    self.device_number = device_number
+    self.inode = inode
+    self.offset = offset
+    self.length = length
+
+  def __str__(self):
+    return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \
+        %(self.device_number, self.inode, self.offset, self.length)
+
+def debug_print(msg):
+  #print(msg)
+  pass
+
+UNDER_LAUNCH = False
+
+def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]):
+  global _PAGE_SIZE
+
+  runs = [
+      PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs,
+              length=_PAGE_SIZE)
+        for pg_entry in page_cache_entries
+  ]
+
+  for r in runs:
+    debug_print(r)
+
+  print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE))
+
+  return runs
+
+def optimize_page_runs(page_runs):
+  new_entries = []
+  last_entry = None
+  for pg_entry in page_runs:
+    if last_entry:
+      if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode:
+        # we are dealing with a run for the same exact file as a previous run.
+        if pg_entry.offset == last_entry.offset + last_entry.length:
+          # trivially contiguous entries. merge them together.
+          last_entry.length += pg_entry.length
+          continue
+    # Default: Add the run without merging it to a previous run.
+    last_entry = pg_entry
+    new_entries.append(pg_entry)
+  return new_entries
+
+def is_filename_matching_filter(file_name, filters=[]):
+  """
+  Blacklist-style regular expression filters.
+
+  :return: True iff file_name has an RE match in one of the filters.
+  """
+  for filt in filters:
+    res = re.search(filt, file_name)
+    if res:
+      return True
+
+  return False
+
+def build_protobuf(page_runs, inode2filename, filters=[]):
+  trace_file = TraceFile()
+  trace_file_index = trace_file.index
+
+  file_id_counter = 0
+  file_id_map = {} # filename -> id
+
+  stats_length_total = 0
+  filename_stats = {} # filename -> total size
+
+  skipped_inode_map = {}
+  filtered_entry_map = {} # filename -> count
+
+  for pg_entry in page_runs:
+    fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode)
+    if not fn:
+      skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1
+      continue
+
+    filename = fn
+
+    if filters and not is_filename_matching_filter(filename, filters):
+      filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1
+      continue
+
+    file_id = file_id_map.get(filename)
+    # file_id could 0, which satisfies "if file_id" and causes duplicate
+    # filename for file id 0.
+    if file_id is None:
+      file_id = file_id_counter
+      file_id_map[filename] = file_id_counter
+      file_id_counter = file_id_counter + 1
+
+      file_index_entry = trace_file_index.entries.add()
+      file_index_entry.id = file_id
+      file_index_entry.file_name = filename
+
+    # already in the file index, add the file entry.
+    file_entry = trace_file.list.entries.add()
+    file_entry.index_id = file_id
+    file_entry.file_length = pg_entry.length
+    stats_length_total += file_entry.file_length
+    file_entry.file_offset = pg_entry.offset
+
+    filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length
+
+  for inode, count in skipped_inode_map.items():
+    print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count))
+
+  print("Stats: Sum of lengths %d" %(stats_length_total))
+
+  if filters:
+    print("Filter: %d total files removed." %(len(filtered_entry_map)))
+
+    for fn, count in filtered_entry_map.items():
+      print("Filter: File '%s' removed '%d' entries." %(fn, count))
+
+  for filename, file_size in filename_stats.items():
+    print("%s,%s" %(filename, file_size))
+
+  return trace_file
+
+def calc_trace_end_time(trace2db: Trace2Db,
+                        trace_duration: Optional[timedelta]) -> float:
+  """
+  Calculates the end time based on the trace duration.
+  The start time is the first receiving mm file map event.
+  The end time is the start time plus the trace duration.
+  All of them are in milliseconds.
+  """
+  # If the duration is not set, assume all time is acceptable.
+  if trace_duration is None:
+    # float('inf')
+    return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf')
+
+  first_event = trace2db.session.query(MmFilemapAddToPageCache).join(
+      MmFilemapAddToPageCache.raw_ftrace_entry).order_by(
+      RawFtraceEntry.timestamp).first()
+
+  # total_seconds() will return a float number.
+  return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds()
+
+def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]):
+  end_time = calc_trace_end_time(trace2db, trace_duration)
+  # SELECT * FROM tbl ORDER BY id;
+  return trace2db.session.query(MmFilemapAddToPageCache).join(
+      MmFilemapAddToPageCache.raw_ftrace_entry).filter(
+      RawFtraceEntry.timestamp <= end_time).order_by(
+      MmFilemapAddToPageCache.id).all()
+
+def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str,
+                                         path_to_tmp_systrace: str) -> None:
+  """ Transforms the systrace file from perfetto trace. """
+  cmd_utils.run_command_nofail([str(TRACECONV_BIN),
+                                'systrace',
+                                path_to_perfetto_trace,
+                                path_to_tmp_systrace])
+
+
+def run(sql_db_path:str,
+        trace_file:str,
+        trace_duration:Optional[timedelta],
+        output_file:str,
+        inode_table:str,
+        filter:List[str]) -> int:
+  trace2db = Trace2Db(sql_db_path)
+  # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache.
+  trace2db.set_raw_ftrace_entry_filter(\
+      lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache')
+  # TODO: parse multiple trace files here.
+  parse_count = trace2db.parse_file_into_db(trace_file)
+
+  mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db,
+                                                              trace_duration)
+  print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows)))
+
+  page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows)
+  print("DONE. Converted %d entries" %(len(page_runs)))
+
+  # TODO: flags to select optimizations.
+  optimized_page_runs = optimize_page_runs(page_runs)
+  print("DONE. Optimized down to %d entries" %(len(optimized_page_runs)))
+
+  print("Build protobuf...")
+  trace_file = build_protobuf(optimized_page_runs, inode_table, filter)
+
+  print("Write protobuf to file...")
+  output_file = open(output_file, 'wb')
+  output_file.write(trace_file.SerializeToString())
+  output_file.close()
+
+  print("DONE")
+
+  # TODO: Silent running mode [no output except on error] for build runs.
+
+  return 0
+
+def main(argv):
+  parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb")
+  parser.add_option('-i', dest='inode_data_file', metavar='FILE',
+                    help='Read cached inode data from a file saved earlier with pagecache.py -d')
+  parser.add_option('-t', dest='trace_file', metavar='FILE',
+                    help='Path to systrace file (trace.html) that will be parsed')
+  parser.add_option('--perfetto-trace', dest='perfetto_trace_file',
+                    metavar='FILE',
+                    help='Path to perfetto trace that will be parsed')
+
+  parser.add_option('--db', dest='sql_db', metavar='FILE',
+                    help='Path to intermediate sqlite3 database [default: in-memory].')
+
+  parser.add_option('-f', dest='filter', action="append", default=[],
+                    help="Add file filter. All file entries not matching one of the filters are discarded.")
+
+  parser.add_option('-l', dest='launch_lock', action="store_true", default=False,
+                    help="Exclude all events not inside launch_lock")
+
+  parser.add_option('-o', dest='output_file', metavar='FILE',
+                    help='Output protobuf file')
+
+  parser.add_option('--duration', dest='trace_duration', action="store",
+                    type=int, help='The duration of trace in milliseconds.')
+
+  options, categories = parser.parse_args(argv[1:])
+
+  # TODO: OptionParser should have some flags to make these mandatory.
+  if not options.inode_data_file:
+    parser.error("-i is required")
+  if not options.trace_file and not options.perfetto_trace_file:
+    parser.error("one of -t or --perfetto-trace is required")
+  if options.trace_file and options.perfetto_trace_file:
+    parser.error("please enter either -t or --perfetto-trace, not both")
+  if not options.output_file:
+    parser.error("-o is required")
+
+  if options.launch_lock:
+    print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.")
+
+  inode_table = Inode2Filename.new_from_filename(options.inode_data_file)
+
+  sql_db_path = ":memory:"
+  if options.sql_db:
+    sql_db_path = options.sql_db
+
+  trace_duration = timedelta(milliseconds=options.trace_duration) if \
+    options.trace_duration is not None else None
+
+  # if the input is systrace
+  if options.trace_file:
+    return run(sql_db_path,
+               options.trace_file,
+               trace_duration,
+               options.output_file,
+               inode_table,
+               options.filter)
+
+  # if the input is perfetto trace
+  # TODO python 3.7 switch to using nullcontext
+  with tempfile.NamedTemporaryFile() as trace_file:
+    transform_perfetto_trace_to_systrace(options.perfetto_trace_file,
+                                         trace_file.name)
+    return run(sql_db_path,
+               trace_file.name,
+               trace_duration,
+               options.output_file,
+               inode_table,
+               options.filter)
+
+if __name__ == '__main__':
+  print(sys.argv)
+  sys.exit(main(sys.argv))
diff --git a/startop/scripts/iorap/compiler_test.py b/startop/scripts/iorap/compiler_test.py
index 1a9f059..d1f11c5 100644
--- a/startop/scripts/iorap/compiler_test.py
+++ b/startop/scripts/iorap/compiler_test.py
@@ -30,7 +30,7 @@
 """
 import os
 
-import compiler
+import compiler_host as compiler
 
 DIR = os.path.abspath(os.path.dirname(__file__))
 TEXTCACHE = os.path.join(DIR, 'test_fixtures/compiler/common_textcache')
diff --git a/startop/scripts/iorap/lib/iorapd_utils.py b/startop/scripts/iorap/lib/iorapd_utils.py
index 0d62180..f6f21fd 100644
--- a/startop/scripts/iorap/lib/iorapd_utils.py
+++ b/startop/scripts/iorap/lib/iorapd_utils.py
@@ -18,10 +18,9 @@
 
 import os
 import sys
-from pathlib import Path
 
-# up to two level, like '../../'
-sys.path.append(Path(os.path.abspath(__file__)).parents[2])
+# up to two level
+sys.path.append(os.path.join(os.path.abspath(__file__),'../..'))
 import lib.cmd_utils as cmd_utils
 
 IORAPID_LIB_DIR = os.path.abspath(os.path.dirname(__file__))
@@ -39,6 +38,22 @@
   # Match logic of 'AppComponentName' in iorap::compiler C++ code.
   return '{}/{}%2F{}.{}'.format(IORAPD_DATA_PATH, package, activity, suffix)
 
+def compile_perfetto_trace_on_device(package: str, activity: str,
+                                     inodes: str) -> bool:
+  """Compiles the perfetto trace using on-device compiler."""
+  passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT,
+                                       'iorapd_compiler_for_app_trace',
+                                       [package, activity, inodes])
+  return passed
+
+def get_iorapd_compiler_trace(package: str, activity: str, dest: str) -> str:
+  """Gets compiler trace to dest file."""
+  src = _iorapd_path_to_data_file(package, activity, 'compiled_trace.pb')
+  passed, _ = cmd_utils.run_shell_command('adb pull "{}" "{}"'.format(src, dest))
+  if not passed:
+    return False
+  return True
+
 def iorapd_compiler_install_trace_file(package: str, activity: str,
                                        input_file: str) -> bool:
   """Installs a compiled trace file.
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 4f6524e..c380d29 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -25,6 +25,7 @@
         "slicer",
     ],
     static_libs: [
+        "libcutils",
         "libtinyxml2",
         "liblog",
         "libutils",
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 1214538..f783aa6 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -37,15 +37,21 @@
 android_test {
     name: "dex-builder-test",
     srcs: [
+        "src/android/startop/test/ApkLayoutCompilerTest.java",
         "src/android/startop/test/DexBuilderTest.java",
         "src/android/startop/test/LayoutCompilerTest.java",
         "src/android/startop/test/TestClass.java",
     ],
     sdk_version: "current",
-    data: [":generate_dex_testcases", ":generate_compiled_layout1", ":generate_compiled_layout2"],
+    data: [
+        ":generate_dex_testcases",
+        ":generate_compiled_layout1",
+        ":generate_compiled_layout2",
+    ],
     static_libs: [
-        "androidx.test.rules",
-        "guava",
+        "androidx.test.core",
+        "androidx.test.runner",
+        "junit",
     ],
     manifest: "AndroidManifest.xml",
     resource_dirs: ["res"],
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
new file mode 100644
index 0000000..230e8df
--- /dev/null
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.startop.test;
+
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
+import android.view.View;
+import dalvik.system.PathClassLoader;
+import java.lang.reflect.Method;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ApkLayoutCompilerTest {
+    static ClassLoader loadDexFile() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
+                ClassLoader.getSystemClassLoader());
+    }
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        // ensure PackageManager has compiled the layouts.
+        Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
+        pm.waitFor();
+    }
+
+    @Test
+    public void loadAndInflateLayout1() throws Exception {
+        ClassLoader dex_file = loadDexFile();
+        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+        Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
+        Context context = InstrumentationRegistry.getTargetContext();
+        layout1.invoke(null, context, R.layout.layout1);
+    }
+
+    @Test
+    public void loadAndInflateLayout2() throws Exception {
+        ClassLoader dex_file = loadDexFile();
+        Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
+        Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
+        Context context = InstrumentationRegistry.getTargetContext();
+        layout2.invoke(null, context, R.layout.layout2);
+    }
+}
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
index d1fe588..6af01f6f 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/DexBuilderTest.java
@@ -14,8 +14,11 @@
 
 package android.startop.test;
 
+import android.content.Context;
+import androidx.test.InstrumentationRegistry;
 import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
index 3dfb20c..b0cf91d 100644
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
+++ b/startop/view_compiler/dex_builder_test/src/android/startop/test/LayoutCompilerTest.java
@@ -15,11 +15,11 @@
 package android.startop.test;
 
 import android.content.Context;
-
 import androidx.test.InstrumentationRegistry;
-
+import android.view.View;
 import dalvik.system.PathClassLoader;
-
+import java.lang.reflect.Method;
+import org.junit.Assert;
 import org.junit.Test;
 
 import java.lang.reflect.Method;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 0e0406d..e4f8d11 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -16,7 +16,6 @@
 
 package android.telecom;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.app.Service;
@@ -33,9 +32,6 @@
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
@@ -75,7 +71,7 @@
  *
  * public void requestRole() {
  *     RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
- *     Intent intent = roleManager.createRequestRoleIntent("android.app.role.CALL_SCREENING");
+ *     Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING);
  *     startActivityForResult(intent, REQUEST_ID);
  * }
  *
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 3548810..0abd9fc 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.telecom.Logging.Session;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telecom.IConnectionService;
 import com.android.internal.telecom.IConnectionServiceAdapter;
@@ -2672,4 +2673,13 @@
             return ++mId;
         }
     }
+
+    /**
+     * Returns this handler, ONLY FOR TESTING.
+     * @hide
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
 }
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 949f7b7..49c3a72 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -391,6 +391,20 @@
         return mCurrentThreadId.get();
     }
 
+    /**
+     * @return A String representation of the active sessions at the time that this method is
+     * called.
+     */
+    @VisibleForTesting
+    public synchronized String printActiveSessions() {
+        StringBuilder message = new StringBuilder();
+        for (ConcurrentHashMap.Entry<Integer, Session> entry : mSessionMapper.entrySet()) {
+            message.append(entry.getValue().printFullSessionTree());
+            message.append("\n");
+        }
+        return message.toString();
+    }
+
     @VisibleForTesting
     public synchronized void cleanupStaleSessions(long timeoutMs) {
         String logMessage = "Stale Sessions Cleaned:\n";
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 61f425e..67b252e 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -3943,9 +3943,10 @@
     }
 
     /**
-     * Contains received SMS cell broadcast messages.
+     * Contains received SMS cell broadcast messages. More details are available in 3GPP TS 23.041.
      * @hide
      */
+    @SystemApi
     public static final class CellBroadcasts implements BaseColumns {
 
         /**
@@ -3957,30 +3958,59 @@
         /**
          * The {@code content://} URI for this table.
          */
+        @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
 
         /**
-         * Message geographical scope.
+         * The id of the subscription which received this cell broadcast message.
+         * <P>Type: INTEGER</P>
+         * @hide
+         */
+        public static final String SUB_ID = "sub_id";
+
+        /**
+         * Message geographical scope. Valid values are:
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE}. meaning the
+         * message is for the radio service cell</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
+         * meaning the message is for the radio service cell and immediately displayed</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_PLMN_WIDE}, meaning the
+         * message is for the PLMN (i.e. MCC/MNC)</li>
+         * <li>{@link android.telephony.SmsCbMessage#GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE},
+         * meaning the message is for the location area (in GSM) or service area (in UMTS)</li>
+         * </ul>
+         *
+         * <p>A message meant for a particular scope is automatically dismissed when the device
+         * exits that scope.</p>
          * <P>Type: INTEGER</P>
          */
         public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
 
         /**
          * Message serial number.
+         * <p>
+         * A 16-bit integer which identifies a particular CBS (cell
+         * broadcast short message service) message. The core network is responsible for
+         * allocating this value, and the value may be managed cyclically (3GPP TS 23.041 section
+         * 9.2.1) once the serial message has been incremented a sufficient number of times.
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERIAL_NUMBER = "serial_number";
 
         /**
-         * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
-         * a broadcast for duplicate detection purposes.
+         * PLMN (i.e. MCC/MNC) of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID}
+         * uniquely identifies a broadcast for duplicate detection purposes.
          * <P>Type: TEXT</P>
          */
         public static final String PLMN = "plmn";
 
         /**
-         * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
-         * Only included if Geographical Scope of message is not PLMN wide (01).
+         * Location area code (LAC).
+         * <p>Code representing location area (GSM) or service area (UMTS) of broadcast sender.
+         * Unused for CDMA. Only included if Geographical Scope of message is not PLMN wide (01).
+         * This value is sent by the network based on the cell tower.
          * <P>Type: INTEGER</P>
          */
         public static final String LAC = "lac";
@@ -3995,23 +4025,29 @@
         /**
          * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_CODE = "message_code";
 
         /**
          * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
          * <P>Type: INTEGER</P>
+         * @hide
          */
         public static final String V1_MESSAGE_IDENTIFIER = "message_id";
 
         /**
-         * Service category (GSM/UMTS: message identifier; CDMA: service category).
+         * Service category which represents the general topic of the message.
+         * <p>
+         * For GSM/UMTS: message identifier (see 3GPP TS 23.041 section 9.4.1.2.2)
+         * For CDMA: a 16-bit CDMA service category (see 3GPP2 C.R1001-D section 9.3)
+         * </p>
          * <P>Type: INTEGER</P>
          */
         public static final String SERVICE_CATEGORY = "service_category";
 
         /**
-         * Message language code.
+         * Message language code. (See 3GPP TS 23.041 section 9.4.1.2.3 for details).
          * <P>Type: TEXT</P>
          */
         public static final String LANGUAGE_CODE = "language";
@@ -4024,6 +4060,7 @@
 
         /**
          * Message delivery time.
+         * <p>This value is a system timestamp using {@link System#currentTimeMillis}</p>
          * <P>Type: INTEGER (long)</P>
          */
         public static final String DELIVERY_TIME = "date";
@@ -4035,25 +4072,36 @@
         public static final String MESSAGE_READ = "read";
 
         /**
-         * Message format (3GPP or 3GPP2).
+         * Message format ({@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP} or
+         * {@link android.telephony.SmsCbMessage#MESSAGE_FORMAT_3GPP2}).
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_FORMAT = "format";
 
         /**
-         * Message priority (including emergency).
+         * Message priority.
+         * <p>This includes
+         * <ul>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_NORMAL}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_INTERACTIVE}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_URGENT}</li>
+         * <li>{@link android.telephony.SmsCbMessage#MESSAGE_PRIORITY_EMERGENCY}</li>
+         * </p>
+         * </ul>
          * <P>Type: INTEGER</P>
          */
         public static final String MESSAGE_PRIORITY = "priority";
 
         /**
-         * ETWS warning type (ETWS alerts only).
+         * ETWS (Earthquake and Tsunami Warning System) warning type (ETWS alerts only).
+         * <p>See {@link android.telephony.SmsCbEtwsInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String ETWS_WARNING_TYPE = "etws_warning_type";
 
         /**
-         * CMAS message class (CMAS alerts only).
+         * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only).
+         * <p>See {@link android.telephony.SmsCbCmasInfo}</p>
          * <P>Type: INTEGER</P>
          */
         public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
@@ -4094,12 +4142,14 @@
         /**
          * The timestamp in millisecond of when the device received the message.
          * <P>Type: BIGINT</P>
+         * @hide
          */
         public static final String RECEIVED_TIME = "received_time";
 
         /**
          * Indicates that whether the message has been broadcasted to the application.
          * <P>Type: BOOLEAN</P>
+         * @hide
          */
         public static final String MESSAGE_BROADCASTED = "message_broadcasted";
 
@@ -4135,12 +4185,27 @@
          * "circle|0,0|100;polygon|0,0|0,1.5|1,1|1,0;circle|100.123,100|200.123"
          *
          * <P>Type: TEXT</P>
+         * @hide
          */
         public static final String GEOMETRIES = "geometries";
 
         /**
-         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * Geo-Fencing Maximum Wait Time in second. The range of the time is [0, 255]. A device
+         * shall allow to determine its position meeting operator policy. If the device is unable to
+         * determine its position meeting operator policy within the GeoFencing Maximum Wait Time,
+         * it shall present the alert to the user and discontinue further positioning determination
+         * for the alert.
+         *
+         * <P>Type: INTEGER</P>
+         * @hide
          */
+        public static final String MAXIMUM_WAIT_TIME = "maximum_wait_time";
+
+        /**
+         * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+         * @hide
+         */
+        @NonNull
         public static final String[] QUERY_COLUMNS = {
                 _ID,
                 GEOGRAPHICAL_SCOPE,
@@ -4166,6 +4231,7 @@
 
         /**
          * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects.
+         * @hide
          */
         public static final String[] QUERY_COLUMNS_FWK = {
                 _ID,
@@ -4188,7 +4254,8 @@
                 CMAS_CERTAINTY,
                 RECEIVED_TIME,
                 MESSAGE_BROADCASTED,
-                GEOMETRIES
+                GEOMETRIES,
+                MAXIMUM_WAIT_TIME
         };
     }
 
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/android/telephony/CallerInfo.java
similarity index 93%
rename from telephony/java/com/android/internal/telephony/CallerInfo.java
rename to telephony/java/android/telephony/CallerInfo.java
index 13539b8..f87ac50 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/android/telephony/CallerInfo.java
@@ -14,8 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package android.telephony;
 
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -31,10 +33,6 @@
 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;
 
@@ -43,6 +41,7 @@
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
 
+import com.android.internal.annotations.VisibleForTesting;
 import java.util.Locale;
 
 
@@ -51,11 +50,14 @@
  *
  * {@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;
 
     /**
@@ -85,49 +87,61 @@
      * 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;
+    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 */
+    /**
+     * 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.
-    @UnsupportedAppUsage
-    public long contactIdOrZero;
+    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.
      */
-    public Uri contactDisplayPhotoUri;
+    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;
 
     /**
@@ -141,6 +155,8 @@
      *
      * The {@link #isCachedPhotoCurrent} flag indicates if the image
      * data needs to be reloaded.
+     *
+     * @hide
      */
     public Drawable cachedPhoto;
     /**
@@ -153,18 +169,23 @@
      *
      * 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?
@@ -180,6 +201,8 @@
      * @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();
@@ -321,6 +344,8 @@
      * @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) {
@@ -346,6 +371,8 @@
      * 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) {
@@ -365,6 +392,8 @@
      * 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) {
@@ -398,6 +427,59 @@
     }
 
     /**
+     * @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.
@@ -425,6 +507,7 @@
 
     /**
      * @return true if the caller info is an emergency number.
+     * @hide
      */
     public boolean isEmergencyNumber() {
         return mIsEmergency;
@@ -432,6 +515,7 @@
 
     /**
      * @return true if the caller info is a voicemail number.
+     * @hide
      */
     public boolean isVoiceMailNumber() {
         return mIsVoiceMail;
@@ -591,6 +675,7 @@
      * @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;
@@ -600,6 +685,8 @@
     /**
      * @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 + "')...");
@@ -657,6 +744,7 @@
         return countryIso;
     }
 
+    /** @hide */
     protected static String getCurrentCountryIso(Context context) {
         return getCurrentCountryIso(context, Locale.getDefault());
     }
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/android/telephony/CallerInfoAsyncQuery.java
similarity index 98%
rename from telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
rename to telephony/java/android/telephony/CallerInfoAsyncQuery.java
index 4e1ff8f..88b471e 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/android/telephony/CallerInfoAsyncQuery.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package android.telephony;
 
 import android.app.ActivityManager;
 import android.content.AsyncQueryHandler;
@@ -31,9 +31,6 @@
 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;
@@ -346,16 +343,16 @@
 
                     // Use the number entered by the user for display.
                     if (!TextUtils.isEmpty(cw.number)) {
-                        mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,
+                        mCallerInfo.setPhoneNumber(PhoneNumberUtils.formatNumber(cw.number,
                                 mCallerInfo.normalizedNumber,
-                                CallerInfo.getCurrentCountryIso(mContext));
+                                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 (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);
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 271195b..1ffed25 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -33,13 +33,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
 
+import dalvik.system.VMRuntime;
+
 import java.lang.ref.WeakReference;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Executor;
 
-import dalvik.system.VMRuntime;
-
 /**
  * A listener class for monitoring changes in specific telephony states
  * on the device, including service state, signal strength, message
@@ -301,11 +301,6 @@
      *  it could be the current active opportunistic subscription in use, or the
      *  subscription user selected as default data subscription in DSDS mode.
      *
-     *  Requires Permission: No permission is required to listen, but notification requires
-     *  {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or the calling
-     *  app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges})
-     *  on any active subscription.
-     *
      *  @see #onActiveDataSubscriptionIdChanged
      */
     public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 0x00400000;
@@ -362,6 +357,30 @@
     @SystemApi
     public static final int LISTEN_IMS_CALL_DISCONNECT_CAUSES              = 0x08000000;
 
+    /**
+     * Listen for the emergency number placed from an outgoing call.
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
+     * @see #onOutgoingEmergencyCall
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+    public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER           = 0x10000000;
+
+    /**
+     * Listen for the emergency number placed from an outgoing SMS.
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
+     *
+     * @see #onOutgoingEmergencySms
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
+    public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER            = 0x20000000;
+
     /*
      * Subscription used to listen to the phone state changes
      * @hide
@@ -844,6 +863,27 @@
     }
 
     /**
+     * Callback invoked when an outgoing call is placed to an emergency number.
+     *
+     * @param placedEmergencyNumber the emergency number {@link EmergencyNumber} the call is placed
+     *                              to.
+     * @hide
+     */
+    public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+        // default implementation empty
+    }
+
+    /**
+     * Callback invoked when an outgoing SMS is placed to an emergency number.
+     *
+     * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
+     * @hide
+     */
+    public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+        // default implementation empty
+    }
+
+    /**
      * Callback invoked when OEM hook raw event is received on the registered subscription.
      * Note, the registration subId comes from {@link TelephonyManager} object which registers
      * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
@@ -1151,7 +1191,6 @@
                             () -> psl.onPhysicalChannelConfigurationChanged(configs)));
         }
 
-        @Override
         public void onEmergencyNumberListChanged(Map emergencyNumberList) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
@@ -1161,6 +1200,24 @@
                             () -> psl.onEmergencyNumberListChanged(emergencyNumberList)));
         }
 
+        public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onOutgoingEmergencyCall(placedEmergencyNumber)));
+        }
+
+        public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
+            PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+            if (psl == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(
+                            () -> psl.onOutgoingEmergencySms(sentEmergencyNumber)));
+        }
+
         public void onPhoneCapabilityChanged(PhoneCapability capability) {
             PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
             if (psl == null) return;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 2651346..a985a6b 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -369,15 +369,16 @@
     /**
      * Create a new ServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
+     * This method is used by PhoneStateIntentReceiver, CellBroadcastReceiver, and maybe by
      * external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created ServiceState
      * @hide
      */
+    @NonNull
     @UnsupportedAppUsage
-    public static ServiceState newFromBundle(Bundle m) {
+    public static ServiceState newFromBundle(@NonNull Bundle m) {
         ServiceState ret;
         ret = new ServiceState();
         ret.setFromNotifierBundle(m);
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java
similarity index 72%
rename from telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
rename to telephony/java/android/telephony/SmsCbCmasInfo.java
index c912924..2c10a09 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
+++ b/telephony/java/android/telephony/SmsCbCmasInfo.java
@@ -16,17 +16,25 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Contains CMAS (Commercial Mobile Alert System) warning notification Type 1 elements for a
+ * {@link SmsCbMessage}.
  * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
  * 3GPP TS 23.041 (for GSM/UMTS).
  *
  * {@hide}
  */
-public class SmsCbCmasInfo implements Parcelable {
+@SystemApi
+public final class SmsCbCmasInfo implements Parcelable {
 
     // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
 
@@ -54,6 +62,21 @@
     /** CMAS category for warning types that are reserved for future extension. */
     public static final int CMAS_CLASS_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CLASS_"},
+            value = {
+                    CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT,
+                    CMAS_CLASS_EXTREME_THREAT,
+                    CMAS_CLASS_SEVERE_THREAT,
+                    CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY,
+                    CMAS_CLASS_REQUIRED_MONTHLY_TEST,
+                    CMAS_CLASS_CMAS_EXERCISE,
+                    CMAS_CLASS_OPERATOR_DEFINED_USE,
+                    CMAS_CLASS_UNKNOWN,
+            })
+    public @interface Class {}
+
     // CMAS alert category (in CDMA type 1 elements record).
 
     /** CMAS alert category: Geophysical including landslide. */
@@ -98,6 +121,26 @@
      */
     public static final int CMAS_CATEGORY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CATEORY_"},
+            value = {
+                    CMAS_CATEGORY_GEO,
+                    CMAS_CATEGORY_MET,
+                    CMAS_CATEGORY_SAFETY,
+                    CMAS_CATEGORY_SECURITY,
+                    CMAS_CATEGORY_RESCUE,
+                    CMAS_CATEGORY_FIRE,
+                    CMAS_CATEGORY_HEALTH,
+                    CMAS_CATEGORY_ENV,
+                    CMAS_CATEGORY_TRANSPORT,
+                    CMAS_CATEGORY_INFRA,
+                    CMAS_CATEGORY_CBRNE,
+                    CMAS_CATEGORY_OTHER,
+                    CMAS_CATEGORY_UNKNOWN,
+            })
+    public @interface Category {}
+
     // CMAS response type (in CDMA type 1 elements record).
 
     /** CMAS response type: Take shelter in place. */
@@ -130,6 +173,22 @@
      */
     public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_RESPONSE_TYPE_"},
+            value = {
+                    CMAS_RESPONSE_TYPE_SHELTER,
+                    CMAS_RESPONSE_TYPE_EVACUATE,
+                    CMAS_RESPONSE_TYPE_PREPARE,
+                    CMAS_RESPONSE_TYPE_EXECUTE,
+                    CMAS_RESPONSE_TYPE_MONITOR,
+                    CMAS_RESPONSE_TYPE_AVOID,
+                    CMAS_RESPONSE_TYPE_ASSESS,
+                    CMAS_RESPONSE_TYPE_NONE,
+                    CMAS_RESPONSE_TYPE_UNKNOWN,
+    })
+    public @interface ResponseType {}
+
     // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS severity type: Extraordinary threat to life or property. */
@@ -145,6 +204,16 @@
      */
     public static final int CMAS_SEVERITY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_SEVERITY_"},
+            value = {
+                    CMAS_SEVERITY_EXTREME,
+                    CMAS_SEVERITY_SEVERE,
+                    CMAS_SEVERITY_UNKNOWN,
+            })
+    public @interface Severity {}
+
     // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS urgency type: Responsive action should be taken immediately. */
@@ -160,6 +229,16 @@
      */
     public static final int CMAS_URGENCY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_URGENCY_"},
+            value = {
+                    CMAS_URGENCY_IMMEDIATE,
+                    CMAS_URGENCY_EXPECTED,
+                    CMAS_URGENCY_UNKNOWN,
+            })
+    public @interface Urgency {}
+
     // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
 
     /** CMAS certainty type: Determined to have occurred or to be ongoing. */
@@ -175,27 +254,38 @@
      */
     public static final int CMAS_CERTAINTY_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CMAS_CERTAINTY_"},
+            value = {
+                    CMAS_CERTAINTY_OBSERVED,
+                    CMAS_CERTAINTY_LIKELY,
+                    CMAS_CERTAINTY_UNKNOWN,
+            })
+    public @interface Certainty {}
+
     /** CMAS message class. */
-    private final int mMessageClass;
+    private final @Class int mMessageClass;
 
     /** CMAS category. */
-    private final int mCategory;
+    private final @Category int mCategory;
 
     /** CMAS response type. */
-    private final int mResponseType;
+    private final @ResponseType int mResponseType;
 
     /** CMAS severity. */
-    private final int mSeverity;
+    private final @Severity int mSeverity;
 
     /** CMAS urgency. */
-    private final int mUrgency;
+    private final @Urgency int mUrgency;
 
     /** CMAS certainty. */
-    private final int mCertainty;
+    private final @Certainty int mCertainty;
 
     /** Create a new SmsCbCmasInfo object with the specified values. */
-    public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
-            int urgency, int certainty) {
+    public SmsCbCmasInfo(@Class int messageClass, @Category int category,
+            @ResponseType int responseType,
+            @Severity int severity, @Urgency int urgency, @Certainty int certainty) {
         mMessageClass = messageClass;
         mCategory = category;
         mResponseType = responseType;
@@ -234,7 +324,7 @@
      * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
      * @return one of the {@code CMAS_CLASS} values
      */
-    public int getMessageClass() {
+    public @Class int getMessageClass() {
         return mMessageClass;
     }
 
@@ -242,7 +332,7 @@
      * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
      * @return one of the {@code CMAS_CATEGORY} values
      */
-    public int getCategory() {
+    public @Category int getCategory() {
         return mCategory;
     }
 
@@ -250,7 +340,7 @@
      * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
      * @return one of the {@code CMAS_RESPONSE_TYPE} values
      */
-    public int getResponseType() {
+    public @ResponseType int getResponseType() {
         return mResponseType;
     }
 
@@ -258,7 +348,7 @@
      * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
      * @return one of the {@code CMAS_SEVERITY} values
      */
-    public int getSeverity() {
+    public @Severity int getSeverity() {
         return mSeverity;
     }
 
@@ -266,15 +356,16 @@
      * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
      * @return one of the {@code CMAS_URGENCY} values
      */
-    public int getUrgency() {
+    public @Urgency int getUrgency() {
         return mUrgency;
     }
 
     /**
      * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+     *
      * @return one of the {@code CMAS_CERTAINTY} values
      */
-    public int getCertainty() {
+    public @Certainty int getCertainty() {
         return mCertainty;
     }
 
@@ -287,6 +378,7 @@
 
     /**
      * Describe the kinds of special objects contained in the marshalled representation.
+     *
      * @return a bitmask indicating this Parcelable contains no special objects
      */
     @Override
@@ -295,8 +387,9 @@
     }
 
     /** Creator for unparcelling objects. */
-    public static final Parcelable.Creator<SmsCbCmasInfo>
-            CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+    @NonNull
+    public static final Parcelable.Creator<SmsCbCmasInfo> CREATOR =
+            new Parcelable.Creator<SmsCbCmasInfo>() {
         @Override
         public SmsCbCmasInfo createFromParcel(Parcel in) {
             return new SmsCbCmasInfo(in);
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java
similarity index 71%
rename from telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
rename to telephony/java/android/telephony/SmsCbEtwsInfo.java
index 15fbc40..2a7f7ad 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
+++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java
@@ -16,22 +16,29 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.telephony.uicc.IccUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
 
 /**
- * Contains information elements for a GSM or UMTS ETWS warning notification.
- * Supported values for each element are defined in 3GPP TS 23.041.
+ * Contains information elements for a GSM or UMTS ETWS (Earthquake and Tsunami Warning
+ * System) warning notification. Supported values for each element are defined in 3GPP TS 23.041.
  *
  * {@hide}
  */
-public class SmsCbEtwsInfo implements Parcelable {
+@SystemApi
+public final class SmsCbEtwsInfo implements Parcelable {
 
     /** ETWS warning type for earthquake. */
     public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
@@ -51,17 +58,30 @@
     /** Unknown ETWS warning type. */
     public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"ETWS_WARNING_TYPE_"},
+            value = {
+                    ETWS_WARNING_TYPE_EARTHQUAKE,
+                    ETWS_WARNING_TYPE_TSUNAMI,
+                    ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI,
+                    ETWS_WARNING_TYPE_TEST_MESSAGE,
+                    ETWS_WARNING_TYPE_OTHER_EMERGENCY,
+                    ETWS_WARNING_TYPE_UNKNOWN,
+            })
+    public @interface WarningType {}
+
     /** One of the ETWS warning type constants defined in this class. */
-    private final int mWarningType;
+    private final @WarningType int mWarningType;
 
     /** Whether or not to activate the emergency user alert tone and vibration. */
-    private final boolean mEmergencyUserAlert;
+    private final boolean mIsEmergencyUserAlert;
 
     /** Whether or not to activate a popup alert. */
-    private final boolean mActivatePopup;
+    private final boolean mIsPopupAlert;
 
     /** Whether ETWS primary message or not/ */
-    private final boolean mPrimary;
+    private final boolean mIsPrimary;
 
     /**
      * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
@@ -70,24 +90,35 @@
      * parceled with the broadcast intent if present, but the timestamp is only computed if an
      * application asks for the individual components.
      */
+    @Nullable
     private final byte[] mWarningSecurityInformation;
 
-    /** Create a new SmsCbEtwsInfo object with the specified values. */
-    public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
-                boolean primary, byte[] warningSecurityInformation) {
+    /**
+     * Create a new SmsCbEtwsInfo object with the specified values.
+     * @param warningType the type of ETWS warning
+     * @param isEmergencyUserAlert whether the warning is an emergency alert, which will activate
+     *                             the user alert tone and vibration
+     * @param isPopupAlert whether the warning will activate a popup alert
+     * @param isPrimary whether this is an ETWS primary message
+     * @param warningSecurityInformation 50-byte security information (for primary notifications
+     *                                   on GSM only).
+     */
+    public SmsCbEtwsInfo(@WarningType int warningType, boolean isEmergencyUserAlert,
+            boolean isPopupAlert,
+            boolean isPrimary, @Nullable byte[] warningSecurityInformation) {
         mWarningType = warningType;
-        mEmergencyUserAlert = emergencyUserAlert;
-        mActivatePopup = activatePopup;
-        mPrimary = primary;
+        mIsEmergencyUserAlert = isEmergencyUserAlert;
+        mIsPopupAlert = isPopupAlert;
+        mIsPrimary = isPrimary;
         mWarningSecurityInformation = warningSecurityInformation;
     }
 
     /** Create a new SmsCbEtwsInfo object from a Parcel. */
     SmsCbEtwsInfo(Parcel in) {
         mWarningType = in.readInt();
-        mEmergencyUserAlert = (in.readInt() != 0);
-        mActivatePopup = (in.readInt() != 0);
-        mPrimary = (in.readInt() != 0);
+        mIsEmergencyUserAlert = (in.readInt() != 0);
+        mIsPopupAlert = (in.readInt() != 0);
+        mIsPrimary = (in.readInt() != 0);
         mWarningSecurityInformation = in.createByteArray();
     }
 
@@ -100,9 +131,9 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mWarningType);
-        dest.writeInt(mEmergencyUserAlert ? 1 : 0);
-        dest.writeInt(mActivatePopup ? 1 : 0);
-        dest.writeInt(mPrimary ? 1 : 0);
+        dest.writeInt(mIsEmergencyUserAlert ? 1 : 0);
+        dest.writeInt(mIsPopupAlert ? 1 : 0);
+        dest.writeInt(mIsPrimary ? 1 : 0);
         dest.writeByteArray(mWarningSecurityInformation);
     }
 
@@ -110,16 +141,17 @@
      * Returns the ETWS warning type.
      * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
      */
-    public int getWarningType() {
+    public @WarningType int getWarningType() {
         return mWarningType;
     }
 
     /**
-     * Returns the ETWS emergency user alert flag.
+     * Returns the ETWS emergency user alert flag. If the ETWS message is an emergency alert, it
+     * will activate an alert tone and vibration.
      * @return true to notify terminal to activate emergency user alert; false otherwise
      */
     public boolean isEmergencyUserAlert() {
-        return mEmergencyUserAlert;
+        return mIsEmergencyUserAlert;
     }
 
     /**
@@ -127,7 +159,7 @@
      * @return true to notify terminal to activate display popup; false otherwise
      */
     public boolean isPopupAlert() {
-        return mActivatePopup;
+        return mIsPopupAlert;
     }
 
     /**
@@ -135,7 +167,7 @@
      * @return true if the message is primary message, otherwise secondary message
      */
     public boolean isPrimary() {
-        return mPrimary;
+        return mIsPrimary;
     }
 
     /**
@@ -188,6 +220,7 @@
      * 3GPP TS 23.041 states that the UE shall ignore this value if received.
      * @return a byte array containing a copy of the primary notification digital signature
      */
+    @Nullable
     public byte[] getPrimaryNotificationSignature() {
         if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
             return null;
@@ -198,7 +231,7 @@
     @Override
     public String toString() {
         return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
-                + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+                + mIsEmergencyUserAlert + ", activatePopup=" + mIsPopupAlert + '}';
     }
 
     /**
@@ -211,6 +244,7 @@
     }
 
     /** Creator for unparcelling objects. */
+    @NonNull
     public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
         @Override
         public SmsCbEtwsInfo createFromParcel(Parcel in) {
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java
similarity index 90%
rename from telephony/java/com/android/internal/telephony/SmsCbLocation.java
rename to telephony/java/android/telephony/SmsCbLocation.java
index 6eb72a8..adf7154 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbLocation.java
+++ b/telephony/java/android/telephony/SmsCbLocation.java
@@ -16,6 +16,9 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,9 +30,11 @@
  *
  * @hide
  */
-public class SmsCbLocation implements Parcelable {
+@SystemApi
+public final class SmsCbLocation implements Parcelable {
 
-    /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+    /** The PLMN. Note that this field may be an empty string. */
+    @NonNull
     private final String mPlmn;
 
     private final int mLac;
@@ -38,6 +43,7 @@
     /**
      * Construct an empty location object. This is used for some test cases, and for
      * cell broadcasts saved in older versions of the database without location info.
+     * @hide
      */
     public SmsCbLocation() {
         mPlmn = "";
@@ -48,6 +54,7 @@
     /**
      * Construct a location object for the PLMN. This class is immutable, so
      * the same object can be reused for multiple broadcasts.
+     * @hide
      */
     public SmsCbLocation(String plmn) {
         mPlmn = plmn;
@@ -58,6 +65,7 @@
     /**
      * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
      * the same object can be reused for multiple broadcasts.
+     * @hide
      */
     public SmsCbLocation(String plmn, int lac, int cid) {
         mPlmn = plmn;
@@ -67,6 +75,7 @@
 
     /**
      * Initialize the object from a Parcel.
+     * @hide
      */
     public SmsCbLocation(Parcel in) {
         mPlmn = in.readString();
@@ -78,6 +87,7 @@
      * Returns the MCC/MNC of the network as a String.
      * @return the PLMN identifier (MCC+MNC) as a String
      */
+    @NonNull
     public String getPlmn() {
         return mPlmn;
     }
@@ -129,7 +139,7 @@
      * @param area the location area to compare with this location
      * @return true if this location is contained within the specified location area
      */
-    public boolean isInLocationArea(SmsCbLocation area) {
+    public boolean isInLocationArea(@NonNull SmsCbLocation area) {
         if (mCid != -1 && mCid != area.mCid) {
             return false;
         }
@@ -147,7 +157,7 @@
      * @param cid the Cell ID to compare with
      * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
      */
-    public boolean isInLocationArea(String plmn, int lac, int cid) {
+    public boolean isInLocationArea(@Nullable String plmn, int lac, int cid) {
         if (!mPlmn.equals(plmn)) {
             return false;
         }
@@ -176,8 +186,9 @@
         dest.writeInt(mCid);
     }
 
-    public static final Parcelable.Creator<SmsCbLocation> CREATOR
-            = new Parcelable.Creator<SmsCbLocation>() {
+    @NonNull
+    public static final Parcelable.Creator<SmsCbLocation> CREATOR =
+            new Parcelable.Creator<SmsCbLocation>() {
         @Override
         public SmsCbLocation createFromParcel(Parcel in) {
             return new SmsCbLocation(in);
diff --git a/telephony/java/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
similarity index 85%
rename from telephony/java/com/android/internal/telephony/SmsCbMessage.java
rename to telephony/java/android/telephony/SmsCbMessage.java
index b9edb9f..c078764 100644
--- a/telephony/java/com/android/internal/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -16,7 +16,10 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.Parcel;
@@ -26,6 +29,8 @@
 import com.android.internal.telephony.CbGeoUtils;
 import com.android.internal.telephony.CbGeoUtils.Geometry;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -70,9 +75,11 @@
  *
  * @hide
  */
-public class SmsCbMessage implements Parcelable {
+@SystemApi
+public final class SmsCbMessage implements Parcelable {
 
-    protected static final String LOG_TAG = "SMSCB";
+    /** @hide */
+    public static final String LOG_TAG = "SMSCB";
 
     /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
@@ -81,17 +88,35 @@
     public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
 
     /** Location / service area wide geographical scope (GSM/UMTS only). */
-    public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+    public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2;
 
     /** Cell wide geographical scope (GSM/UMTS only). */
     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
 
+    /** @hide */
+    @IntDef(prefix = { "GEOGRAPHICAL_SCOPE_" }, value = {
+            GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE,
+            GEOGRAPHICAL_SCOPE_PLMN_WIDE,
+            GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE,
+            GEOGRAPHICAL_SCOPE_CELL_WIDE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GeographicalScope {}
+
     /** GSM or UMTS format cell broadcast. */
     public static final int MESSAGE_FORMAT_3GPP = 1;
 
     /** CDMA format cell broadcast. */
     public static final int MESSAGE_FORMAT_3GPP2 = 2;
 
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_FORMAT_" }, value = {
+            MESSAGE_FORMAT_3GPP,
+            MESSAGE_FORMAT_3GPP2
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessageFormat {}
+
     /** Normal message priority. */
     public static final int MESSAGE_PRIORITY_NORMAL = 0;
 
@@ -104,6 +129,22 @@
     /** Emergency message priority. */
     public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
 
+    /** @hide */
+    @IntDef(prefix = { "MESSAGE_PRIORITY_" }, value = {
+            MESSAGE_PRIORITY_NORMAL,
+            MESSAGE_PRIORITY_INTERACTIVE,
+            MESSAGE_PRIORITY_URGENT,
+            MESSAGE_PRIORITY_EMERGENCY,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MessagePriority {}
+
+    /**
+     * ATIS-0700041 Section 5.2.8 WAC Geo-Fencing Maximum Wait Time Table 12.
+     * @hide
+     */
+    public static final int MAXIMUM_WAIT_TIME_NOT_SET = 255;
+
     /** Format of this message (for interpretation of service category values). */
     private final int mMessageFormat;
 
@@ -123,6 +164,7 @@
      * message is not binary 01, the Location Area is included for comparison. If the GS is
      * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
      */
+    @NonNull
     private final SmsCbLocation mLocation;
 
     /**
@@ -133,20 +175,32 @@
     private final int mServiceCategory;
 
     /** Message language, as a two-character string, e.g. "en". */
+    @Nullable
     private final String mLanguage;
 
     /** Message body, as a String. */
+    @Nullable
     private final String mBody;
 
     /** Message priority (including emergency priority). */
     private final int mPriority;
 
     /** ETWS warning notification information (ETWS warnings only). */
+    @Nullable
     private final SmsCbEtwsInfo mEtwsWarningInfo;
 
     /** CMAS warning notification information (CMAS warnings only). */
+    @Nullable
     private final SmsCbCmasInfo mCmasWarningInfo;
 
+    /**
+     * Geo-Fencing Maximum Wait Time in second, a device shall allow to determine its position
+     * meeting operator policy. If the device is unable to determine its position meeting operator
+     * policy within the GeoFencing Maximum Wait Time, it shall present the alert to the user and
+     * discontinue further positioning determination for the alert.
+     */
+    private final int mMaximumWaitTimeSec;
+
     /** UNIX timestamp of when the message was received. */
     private final long mReceivedTimeMillis;
 
@@ -157,21 +211,23 @@
      * Create a new SmsCbMessage with the specified data.
      */
     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
-            SmsCbLocation location, int serviceCategory, String language, String body,
-            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+            @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
+            @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
+            @Nullable SmsCbCmasInfo cmasWarningInfo) {
 
         this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
-                body, priority, etwsWarningInfo, cmasWarningInfo, null /* geometries */,
-                System.currentTimeMillis());
+                body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */,
+                null /* geometries */, System.currentTimeMillis());
     }
 
     /**
      * Create a new {@link SmsCbMessage} with the warning area coordinates information.
+     * @hide
      */
     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
             SmsCbLocation location, int serviceCategory, String language, String body,
             int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo,
-            List<Geometry> geometries, long receivedTimeMillis) {
+            int maximumWaitTimeSec, List<Geometry> geometries, long receivedTimeMillis) {
         mMessageFormat = messageFormat;
         mGeographicalScope = geographicalScope;
         mSerialNumber = serialNumber;
@@ -184,10 +240,14 @@
         mCmasWarningInfo = cmasWarningInfo;
         mReceivedTimeMillis = receivedTimeMillis;
         mGeometries = geometries;
+        mMaximumWaitTimeSec = maximumWaitTimeSec;
     }
 
-    /** Create a new SmsCbMessage object from a Parcel. */
-    public SmsCbMessage(Parcel in) {
+    /**
+     * Create a new SmsCbMessage object from a Parcel.
+     * @hide
+     */
+    public SmsCbMessage(@NonNull Parcel in) {
         mMessageFormat = in.readInt();
         mGeographicalScope = in.readInt();
         mSerialNumber = in.readInt();
@@ -217,6 +277,7 @@
         mReceivedTimeMillis = in.readLong();
         String geoStr = in.readString();
         mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
+        mMaximumWaitTimeSec = in.readInt();
     }
 
     /**
@@ -250,10 +311,12 @@
         dest.writeLong(mReceivedTimeMillis);
         dest.writeString(
                 mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
+        dest.writeInt(mMaximumWaitTimeSec);
     }
 
-    public static final Parcelable.Creator<SmsCbMessage> CREATOR
-            = new Parcelable.Creator<SmsCbMessage>() {
+    @NonNull
+    public static final Parcelable.Creator<SmsCbMessage> CREATOR =
+            new Parcelable.Creator<SmsCbMessage>() {
         @Override
         public SmsCbMessage createFromParcel(Parcel in) {
             return new SmsCbMessage(in);
@@ -270,7 +333,7 @@
      *
      * @return Geographical scope
      */
-    public int getGeographicalScope() {
+    public @GeographicalScope int getGeographicalScope() {
         return mGeographicalScope;
     }
 
@@ -294,7 +357,8 @@
      *
      * @return the geographical location code for duplicate message detection
      */
-    public SmsCbLocation getLocation() {
+    @NonNull
+    public android.telephony.SmsCbLocation getLocation() {
         return mLocation;
     }
 
@@ -315,6 +379,7 @@
      *
      * @return Language code
      */
+    @Nullable
     public String getLanguageCode() {
         return mLanguage;
     }
@@ -324,6 +389,7 @@
      *
      * @return Body, or null
      */
+    @Nullable
     public String getMessageBody() {
         return mBody;
     }
@@ -332,6 +398,7 @@
      * Get the warning area coordinates information represent by polygons and circles.
      * @return a list of geometries, {@link Nullable} means there is no coordinate information
      * associated to this message.
+     * @hide
      */
     @Nullable
     public List<Geometry> getGeometries() {
@@ -339,6 +406,15 @@
     }
 
     /**
+     * Get the Geo-Fencing Maximum Wait Time.
+     * @return the time in second.
+     * @hide
+     */
+    public int getMaximumWaitingTime() {
+        return mMaximumWaitTimeSec;
+    }
+
+    /**
      * Get the time when this message was received.
      * @return the time in millisecond
      */
@@ -350,7 +426,7 @@
      * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
      * @return an integer representing 3GPP or 3GPP2 message format
      */
-    public int getMessageFormat() {
+    public @MessageFormat int getMessageFormat() {
         return mMessageFormat;
     }
 
@@ -360,7 +436,7 @@
      * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
      * @return an integer representing the message priority
      */
-    public int getMessagePriority() {
+    public @MessagePriority int getMessagePriority() {
         return mPriority;
     }
 
@@ -373,6 +449,7 @@
      *
      * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
      */
+    @Nullable
     public SmsCbEtwsInfo getEtwsWarningInfo() {
         return mEtwsWarningInfo;
     }
@@ -387,13 +464,14 @@
      *
      * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
      */
+    @Nullable
     public SmsCbCmasInfo getCmasWarningInfo() {
         return mCmasWarningInfo;
     }
 
     /**
      * Return whether this message is an emergency (PWS) message type.
-     * @return true if the message is a public warning notification; false otherwise
+     * @return true if the message is an emergency notification; false otherwise
      */
     public boolean isEmergencyMessage() {
         return mPriority == MESSAGE_PRIORITY_EMERGENCY;
@@ -423,6 +501,7 @@
                 + ", priority=" + mPriority
                 + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
                 + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
+                + ", maximumWaitingTime = " + mMaximumWaitTimeSec
                 + ", geo=" + (mGeometries != null
                 ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
                 + '}';
@@ -440,6 +519,7 @@
     /**
      * @return the {@link ContentValues} instance that includes the cell broadcast data.
      */
+    @NonNull
     public ContentValues getContentValues() {
         ContentValues cv = new ContentValues(16);
         cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
@@ -482,6 +562,8 @@
             cv.put(CellBroadcasts.GEOMETRIES, (String) null);
         }
 
+        cv.put(CellBroadcasts.MAXIMUM_WAIT_TIME, mMaximumWaitTimeSec);
+
         return cv;
     }
 
@@ -491,7 +573,8 @@
      * @return a {@link SmsCbMessage} instance.
      * @throws IllegalArgumentException if one of the required columns is missing
      */
-    public static SmsCbMessage createFromCursor(Cursor cursor) {
+    @NonNull
+    public static SmsCbMessage createFromCursor(@NonNull Cursor cursor) {
         int geoScope = cursor.getInt(
                 cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
         int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
@@ -590,17 +673,21 @@
         List<Geometry> geometries =
                 geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
 
-        long receivedTimeSec = cursor.getLong(
+        long receivedTimeMillis = cursor.getLong(
                 cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
 
+        int maximumWaitTimeSec = cursor.getInt(
+                cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME));
+
         return new SmsCbMessage(format, geoScope, serialNum, location, category,
-                language, body, priority, etwsInfo, cmasInfo, geometries, receivedTimeSec);
+                language, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries,
+                receivedTimeMillis);
     }
 
     /**
      * @return {@code True} if this message needs geo-fencing check.
      */
     public boolean needGeoFencingCheck() {
-        return mGeometries != null;
+        return mMaximumWaitTimeSec > 0 && mGeometries != null;
     }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index cd0f57e..8c14cb4 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1670,15 +1670,14 @@
      *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage]
      * @return true if successful, false otherwise
      * @see #disableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean enableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
@@ -1717,16 +1716,15 @@
      *
      * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+     * @param ranType the message format as defined in {@link SmsCbMessage}
      * @return true if successful, false otherwise
      *
      * @see #enableCellBroadcast(int, int)
      *
      * {@hide}
      */
-    public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+    public boolean disableCellBroadcast(int messageIdentifier,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         try {
@@ -1746,8 +1744,8 @@
 
     /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
-     * message identifier range and RAN type. The RAN type specify this message ID
-     * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+     * message identifier range and RAN type. The RAN type specifies if this message ID
+     * belongs to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
      * the same message identifier, they must both disable it for the device to stop
      * receiving those messages. All received messages will be broadcast in an
      * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
@@ -1759,26 +1757,29 @@
      * dialog. If this method is called on a device that has multiple active subscriptions, this
      * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
      * default subscription is defined, the subscription ID associated with this message will be
-     * INVALID, which will result in the operation being completed on the subscription associated
-     * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
-     * operation is performed on the correct subscription.
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, which will result in the operation
+     * being completed on the subscription associated with logical slot 0. Use
+     * {@link #getSmsManagerForSubscriptionId(int)} to ensure the operation is performed on the
+     * correct subscription.
      * </p>
      *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      * @see #disableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
@@ -1818,22 +1819,24 @@
      * operation is performed on the correct subscription.
      * </p>
      *
+     * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST}</p>
+     *
      * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
      * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
      * or C.R1001-G (3GPP2)
-     * @param ranType as defined in class SmsManager, the value can be one of these:
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
-     *    android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
-     * @return true if successful, false otherwise
+     * @param ranType the message format as defined in {@link SmsCbMessage}
+     * @return true if successful, false if the modem reports a failure (e.g. the given range or
+     * RAN type is invalid).
      *
      * @see #enableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+    @SystemApi
+    public boolean disableCellBroadcastRange(int startMessageId, int endMessageId,
+            @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
 
         if (endMessageId < startMessageId) {
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index bb2269f..f527bc3 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -578,9 +578,11 @@
         PackageManager packageManager = context.getPackageManager();
         PackageInfo packageInfo;
         try {
-            packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            packageInfo = packageManager.getPackageInfo(packageName,
+                PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
-            throw new IllegalArgumentException("Unknown package: " + packageName, e);
+            Log.d("SubscriptionInfo", "canManageSubscription: Unknown package: " + packageName, e);
+            return false;
         }
         for (UiccAccessRule rule : allAccessRules) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
@@ -612,7 +614,9 @@
      */
     public @Nullable List<UiccAccessRule> getAllAccessRules() {
         List<UiccAccessRule> merged = new ArrayList<>();
-        if (mNativeAccessRules != null) merged.addAll(getAccessRules());
+        if (mNativeAccessRules != null) {
+            merged.addAll(getAccessRules());
+        }
         if (mCarrierConfigAccessRules != null) {
             merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
         }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index d93d1d6a..51de903 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2291,14 +2291,19 @@
     }
 
     /**
-     * Returns the resources associated with Subscription.
+     * Returns the {@link Resources} from the given {@link Context} for the MCC/MNC associated with
+     * the subscription. If the subscription ID is invalid, the base resources are returned instead.
+     *
+     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *
      * @param context Context object
-     * @param subId Subscription Id of Subscription who's resources are required
+     * @param subId Subscription Id of Subscription whose resources are required
      * @return Resources associated with Subscription.
      * @hide
      */
-    @UnsupportedAppUsage
-    public static Resources getResourcesForSubId(Context context, int subId) {
+    @NonNull
+    @SystemApi
+    public static Resources getResourcesForSubId(@NonNull Context context, int subId) {
         return getResourcesForSubId(context, subId, false);
     }
 
@@ -2690,15 +2695,17 @@
      * @hide
      */
     public boolean canManageSubscription(SubscriptionInfo info, String packageName) {
-        if (info.getAllAccessRules() == null) {
+        if (info == null || info.getAllAccessRules() == null) {
             return false;
         }
         PackageManager packageManager = mContext.getPackageManager();
         PackageInfo packageInfo;
         try {
-            packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+            packageInfo = packageManager.getPackageInfo(packageName,
+                PackageManager.GET_SIGNING_CERTIFICATES);
         } catch (PackageManager.NameNotFoundException e) {
-            throw new IllegalArgumentException("Unknown package: " + packageName, e);
+            logd("Unknown package: " + packageName);
+            return false;
         }
         for (UiccAccessRule rule : info.getAllAccessRules()) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 475563d..85b54872 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;
     }
 
     //
@@ -3132,19 +3153,62 @@
      */
     @SystemApi
     public int getSimCardState() {
-        int simCardState = getSimState();
-        switch (simCardState) {
+        int simState = getSimState();
+        return getSimCardStateFromSimState(simState);
+    }
+
+    /**
+     * Returns a constant indicating the state of the device SIM card in a physical slot.
+     *
+     * @param physicalSlotIndex physical slot index
+     *
+     * @see #SIM_STATE_UNKNOWN
+     * @see #SIM_STATE_ABSENT
+     * @see #SIM_STATE_CARD_IO_ERROR
+     * @see #SIM_STATE_CARD_RESTRICTED
+     * @see #SIM_STATE_PRESENT
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getSimCardState(int physicalSlotIndex) {
+        int simState = getSimState(getLogicalSlotIndex(physicalSlotIndex));
+        return getSimCardStateFromSimState(simState);
+    }
+
+    /**
+     * Converts SIM state to SIM card state.
+     * @param simState
+     * @return SIM card state
+     */
+    private int getSimCardStateFromSimState(int simState) {
+        switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
             case SIM_STATE_CARD_IO_ERROR:
             case SIM_STATE_CARD_RESTRICTED:
-                return simCardState;
+                return simState;
             default:
                 return SIM_STATE_PRESENT;
         }
     }
 
     /**
+     * Converts a physical slot index to logical slot index.
+     * @param physicalSlotIndex physical slot index
+     * @return logical slot index
+     */
+    private int getLogicalSlotIndex(int physicalSlotIndex) {
+        UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+        if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+            return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
+        }
+
+        return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+    }
+
+    /**
      * Returns a constant indicating the state of the card applications on the default SIM card.
      *
      * @see #SIM_STATE_UNKNOWN
@@ -3159,8 +3223,41 @@
      */
     @SystemApi
     public int getSimApplicationState() {
-        int simApplicationState = getSimStateIncludingLoaded();
-        switch (simApplicationState) {
+        int simState = getSimStateIncludingLoaded();
+        return getSimApplicationStateFromSimState(simState);
+    }
+
+    /**
+     * Returns a constant indicating the state of the card applications on the device SIM card in
+     * a physical slot.
+     *
+     * @param physicalSlotIndex physical slot index
+     *
+     * @see #SIM_STATE_UNKNOWN
+     * @see #SIM_STATE_PIN_REQUIRED
+     * @see #SIM_STATE_PUK_REQUIRED
+     * @see #SIM_STATE_NETWORK_LOCKED
+     * @see #SIM_STATE_NOT_READY
+     * @see #SIM_STATE_PERM_DISABLED
+     * @see #SIM_STATE_LOADED
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public int getSimApplicationState(int physicalSlotIndex) {
+        int simState =
+                SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex));
+        return getSimApplicationStateFromSimState(simState);
+    }
+
+    /**
+     * Converts SIM state to SIM application state.
+     * @param simState
+     * @return SIM application state
+     */
+    private int getSimApplicationStateFromSimState(int simState) {
+        switch (simState) {
             case SIM_STATE_UNKNOWN:
             case SIM_STATE_ABSENT:
             case SIM_STATE_CARD_IO_ERROR:
@@ -3171,14 +3268,39 @@
                 // NOT_READY to either LOCKED or LOADED.
                 return SIM_STATE_NOT_READY;
             default:
-                return simApplicationState;
+                return simState;
         }
     }
 
+
     /**
-     * Returns a constant indicating the state of the device SIM card in a slot.
+     * Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
+     * on the UICC card.
      *
-     * @param slotIndex
+     * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
+     *
+     * @param appType the uicc app type like {@link APPTYPE_CSIM}
+     * @return true if the specified type of application in UICC CARD or false if no uicc or error.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isApplicationOnUicc(@UiccAppType int appType) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.isApplicationOnUicc(getSubId(), appType);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#isApplicationOnUicc", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a constant indicating the state of the device SIM card in a logical slot.
+     *
+     * @param slotIndex logical slot index
      *
      * @see #SIM_STATE_UNKNOWN
      * @see #SIM_STATE_ABSENT
@@ -6524,11 +6646,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();
     }
 
     /**
@@ -8554,7 +8672,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) {
@@ -8567,7 +8690,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,
@@ -8687,6 +8817,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) {
@@ -8723,6 +8854,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
@@ -8742,6 +8875,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
@@ -8757,6 +8891,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
@@ -8777,6 +8912,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/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
index 010ad2b..5f2f75d 100644
--- a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
+++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
@@ -16,6 +16,7 @@
 
 package android.telephony.cdma;
 
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -28,7 +29,7 @@
  *
  * {@hide}
  */
-public class CdmaSmsCbProgramData implements Parcelable {
+public final class CdmaSmsCbProgramData implements Parcelable {
 
     /** Delete the specified service category from the list of enabled categories. */
     public static final int OPERATION_DELETE_CATEGORY   = 0;
@@ -95,7 +96,7 @@
 
     /** Create a new CdmaSmsCbProgramData object with the specified values. */
     public CdmaSmsCbProgramData(int operation, int category, int language, int maxMessages,
-            int alertOption, String categoryName) {
+            int alertOption, @NonNull String categoryName) {
         mOperation = operation;
         mCategory = category;
         mLanguage = language;
@@ -174,6 +175,7 @@
      * Returns the service category name, in the language specified by {@link #getLanguage()}.
      * @return an optional service category name
      */
+    @NonNull
     public String getCategoryName() {
         return mCategoryName;
     }
diff --git a/telephony/java/android/telephony/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/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 3a9979d..3562880 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -201,15 +201,20 @@
     }
 
     /**
-     * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask.
-     * <p>
-     * Typically this class is not used directly, but rather extended in subclasses of
+     * Contains the IMS capabilities defined and supported by an ImsFeature in the form of a
+     * bit-mask.
+     *
+     * @deprecated This class is not used directly, but rather extended in subclasses of
      * {@link ImsFeature} to provide service specific capabilities.
+     * @see MmTelFeature.MmTelCapabilities
      * @hide
      */
-    @SystemApi
+    // Not Actually deprecated, but we need to remove it from the @SystemApi surface.
+    @Deprecated
+    @SystemApi // SystemApi only because it was leaked through type usage in a previous release.
     public static class Capabilities {
         /** @deprecated Use getters and accessors instead. */
+        // Not actually deprecated, but we need to remove it from the @SystemApi surface eventually.
         protected int mCapabilities = 0;
 
         /**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 20c191d..ceb4704 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -242,8 +242,8 @@
          * @param capabilities The capabilities that are supported for MmTel in the form of a
          *                     bitfield.
          */
-        public MmTelCapabilities(int capabilities) {
-            mCapabilities = capabilities;
+        public MmTelCapabilities(@MmTelCapability int capabilities) {
+            super(capabilities);
         }
 
         @IntDef(flag = true,
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/CbGeoUtils.java b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
index 73dd822..0b73252 100644
--- a/telephony/java/com/android/internal/telephony/CbGeoUtils.java
+++ b/telephony/java/com/android/internal/telephony/CbGeoUtils.java
@@ -299,7 +299,8 @@
      * @return the encoded string.
      */
     @NonNull
-    public static String encodeGeometriesToString(@NonNull List<Geometry> geometries) {
+    public static String encodeGeometriesToString(List<Geometry> geometries) {
+        if (geometries == null || geometries.isEmpty()) return "";
         return geometries.stream()
                 .map(geometry -> encodeGeometryToString(geometry))
                 .filter(encodedStr -> !TextUtils.isEmpty(encodedStr))
diff --git a/telephony/java/com/android/internal/telephony/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/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/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
index dca4e6b..6eea118 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -104,7 +104,7 @@
                     header.getSerialNumber(), location, header.getServiceCategory(), null,
                     getEtwsPrimaryMessage(context, header.getEtwsInfo().getWarningType()),
                     SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, header.getEtwsInfo(),
-                    header.getCmasInfo(), null /* geometries */, receivedTimeMillis);
+                    header.getCmasInfo(), 0, null /* geometries */, receivedTimeMillis);
         } else if (header.isUmtsFormat()) {
             // UMTS format has only 1 PDU
             byte[] pdu = pdus[0];
@@ -120,9 +120,13 @@
 
             // Has Warning Area Coordinates information
             List<Geometry> geometries = null;
+            int maximumWaitingTimeSec = 255;
             if (pdu.length > wacDataOffset) {
                 try {
-                    geometries = parseWarningAreaCoordinates(pdu, wacDataOffset);
+                    Pair<Integer, List<Geometry>> wac = parseWarningAreaCoordinates(pdu,
+                            wacDataOffset);
+                    maximumWaitingTimeSec = wac.first;
+                    geometries = wac.second;
                 } catch (Exception ex) {
                     // Catch the exception here, the message will be considered as having no WAC
                     // information which means the message will be broadcasted directly.
@@ -133,7 +137,8 @@
             return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, body, priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), geometries, receivedTimeMillis);
+                    header.getEtwsInfo(), header.getCmasInfo(), maximumWaitingTimeSec, geometries,
+                    receivedTimeMillis);
         } else {
             String language = null;
             StringBuilder sb = new StringBuilder();
@@ -148,7 +153,7 @@
             return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
                     header.getGeographicalScope(), header.getSerialNumber(), location,
                     header.getServiceCategory(), language, sb.toString(), priority,
-                    header.getEtwsInfo(), header.getCmasInfo(), null /* geometries */,
+                    header.getEtwsInfo(), header.getCmasInfo(), 0, null /* geometries */,
                     receivedTimeMillis);
         }
     }
@@ -197,7 +202,17 @@
         }
     }
 
-    private static List<Geometry> parseWarningAreaCoordinates(byte[] pdu, int wacOffset) {
+    /**
+     * Parse the broadcast area and maximum wait time from the Warning Area Coordinates TLV.
+     *
+     * @param pdu Warning Area Coordinates TLV.
+     * @param wacOffset the offset of Warning Area Coordinates TLV.
+     * @return a pair with the first element is maximum wait time and the second is the broadcast
+     * area. The default value of the maximum wait time is 255 which means use the device default
+     * value.
+     */
+    private static Pair<Integer, List<Geometry>> parseWarningAreaCoordinates(
+            byte[] pdu, int wacOffset) {
         // little-endian
         int wacDataLength = (pdu[wacOffset + 1] << 8) | pdu[wacOffset];
         int offset = wacOffset + 2;
@@ -209,6 +224,8 @@
 
         BitStreamReader bitReader = new BitStreamReader(pdu, offset);
 
+        int maximumWaitTimeSec = SmsCbMessage.MAXIMUM_WAIT_TIME_NOT_SET;
+
         List<Geometry> geo = new ArrayList<>();
         int remainedBytes = wacDataLength;
         while (remainedBytes > 0) {
@@ -220,8 +237,7 @@
 
             switch (type) {
                 case CbGeoUtils.GEO_FENCING_MAXIMUM_WAIT_TIME:
-                    // TODO: handle the maximum wait time in cell broadcast provider.
-                    int maximumWaitTimeSec = bitReader.read(8);
+                    maximumWaitTimeSec = bitReader.read(8);
                     break;
                 case CbGeoUtils.GEOMETRY_TYPE_POLYGON:
                     List<LatLng> latLngs = new ArrayList<>();
@@ -247,7 +263,7 @@
                     throw new IllegalArgumentException("Unsupported geoType = " + type);
             }
         }
-        return geo;
+        return new Pair(maximumWaitTimeSec, geo);
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index da32c8c..28b331b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -1496,7 +1496,7 @@
      *
      * @return true if this is a USIM data download message; false otherwise
      */
-    boolean isUsimDataDownload() {
+    public boolean isUsimDataDownload() {
         return messageClass == MessageClass.CLASS_2 &&
                 (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
     }
diff --git a/telephony/java/com/google/android/mms/ContentType.java b/telephony/java/com/google/android/mms/ContentType.java
new file mode 100644
index 0000000..12e4b7e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/ContentType.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+
+public class ContentType {
+    public static final String MMS_MESSAGE       = "application/vnd.wap.mms-message";
+    // The phony content type for generic PDUs (e.g. ReadOrig.ind,
+    // Notification.ind, Delivery.ind).
+    public static final String MMS_GENERIC       = "application/vnd.wap.mms-generic";
+    public static final String MULTIPART_MIXED   = "application/vnd.wap.multipart.mixed";
+    public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related";
+    public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative";
+
+    public static final String TEXT_PLAIN        = "text/plain";
+    public static final String TEXT_HTML         = "text/html";
+    public static final String TEXT_VCALENDAR    = "text/x-vCalendar";
+    public static final String TEXT_VCARD        = "text/x-vCard";
+
+    public static final String IMAGE_UNSPECIFIED = "image/*";
+    public static final String IMAGE_JPEG        = "image/jpeg";
+    public static final String IMAGE_JPG         = "image/jpg";
+    public static final String IMAGE_GIF         = "image/gif";
+    public static final String IMAGE_WBMP        = "image/vnd.wap.wbmp";
+    public static final String IMAGE_PNG         = "image/png";
+    public static final String IMAGE_X_MS_BMP    = "image/x-ms-bmp";
+
+    public static final String AUDIO_UNSPECIFIED = "audio/*";
+    public static final String AUDIO_AAC         = "audio/aac";
+    public static final String AUDIO_AMR         = "audio/amr";
+    public static final String AUDIO_IMELODY     = "audio/imelody";
+    public static final String AUDIO_MID         = "audio/mid";
+    public static final String AUDIO_MIDI        = "audio/midi";
+    public static final String AUDIO_MP3         = "audio/mp3";
+    public static final String AUDIO_MPEG3       = "audio/mpeg3";
+    public static final String AUDIO_MPEG        = "audio/mpeg";
+    public static final String AUDIO_MPG         = "audio/mpg";
+    public static final String AUDIO_MP4         = "audio/mp4";
+    public static final String AUDIO_X_MID       = "audio/x-mid";
+    public static final String AUDIO_X_MIDI      = "audio/x-midi";
+    public static final String AUDIO_X_MP3       = "audio/x-mp3";
+    public static final String AUDIO_X_MPEG3     = "audio/x-mpeg3";
+    public static final String AUDIO_X_MPEG      = "audio/x-mpeg";
+    public static final String AUDIO_X_MPG       = "audio/x-mpg";
+    public static final String AUDIO_3GPP        = "audio/3gpp";
+    public static final String AUDIO_X_WAV       = "audio/x-wav";
+    public static final String AUDIO_OGG         = "application/ogg";
+    public static final String AUDIO_OGG2        = "audio/ogg";
+
+    public static final String VIDEO_UNSPECIFIED = "video/*";
+    public static final String VIDEO_3GPP        = "video/3gpp";
+    public static final String VIDEO_3G2         = "video/3gpp2";
+    public static final String VIDEO_H263        = "video/h263";
+    public static final String VIDEO_MP4         = "video/mp4";
+
+    public static final String APP_SMIL          = "application/smil";
+    public static final String APP_WAP_XHTML     = "application/vnd.wap.xhtml+xml";
+    public static final String APP_XHTML         = "application/xhtml+xml";
+
+    public static final String APP_DRM_CONTENT   = "application/vnd.oma.drm.content";
+    public static final String APP_DRM_MESSAGE   = "application/vnd.oma.drm.message";
+
+    private static final ArrayList<String> sSupportedContentTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedImageTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedAudioTypes = new ArrayList<String>();
+    private static final ArrayList<String> sSupportedVideoTypes = new ArrayList<String>();
+
+    static {
+        sSupportedContentTypes.add(TEXT_PLAIN);
+        sSupportedContentTypes.add(TEXT_HTML);
+        sSupportedContentTypes.add(TEXT_VCALENDAR);
+        sSupportedContentTypes.add(TEXT_VCARD);
+
+        sSupportedContentTypes.add(IMAGE_JPEG);
+        sSupportedContentTypes.add(IMAGE_GIF);
+        sSupportedContentTypes.add(IMAGE_WBMP);
+        sSupportedContentTypes.add(IMAGE_PNG);
+        sSupportedContentTypes.add(IMAGE_JPG);
+        sSupportedContentTypes.add(IMAGE_X_MS_BMP);
+        //supportedContentTypes.add(IMAGE_SVG); not yet supported.
+
+        sSupportedContentTypes.add(AUDIO_AAC);
+        sSupportedContentTypes.add(AUDIO_AMR);
+        sSupportedContentTypes.add(AUDIO_IMELODY);
+        sSupportedContentTypes.add(AUDIO_MID);
+        sSupportedContentTypes.add(AUDIO_MIDI);
+        sSupportedContentTypes.add(AUDIO_MP3);
+        sSupportedContentTypes.add(AUDIO_MP4);
+        sSupportedContentTypes.add(AUDIO_MPEG3);
+        sSupportedContentTypes.add(AUDIO_MPEG);
+        sSupportedContentTypes.add(AUDIO_MPG);
+        sSupportedContentTypes.add(AUDIO_X_MID);
+        sSupportedContentTypes.add(AUDIO_X_MIDI);
+        sSupportedContentTypes.add(AUDIO_X_MP3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG3);
+        sSupportedContentTypes.add(AUDIO_X_MPEG);
+        sSupportedContentTypes.add(AUDIO_X_MPG);
+        sSupportedContentTypes.add(AUDIO_X_WAV);
+        sSupportedContentTypes.add(AUDIO_3GPP);
+        sSupportedContentTypes.add(AUDIO_OGG);
+        sSupportedContentTypes.add(AUDIO_OGG2);
+
+        sSupportedContentTypes.add(VIDEO_3GPP);
+        sSupportedContentTypes.add(VIDEO_3G2);
+        sSupportedContentTypes.add(VIDEO_H263);
+        sSupportedContentTypes.add(VIDEO_MP4);
+
+        sSupportedContentTypes.add(APP_SMIL);
+        sSupportedContentTypes.add(APP_WAP_XHTML);
+        sSupportedContentTypes.add(APP_XHTML);
+
+        sSupportedContentTypes.add(APP_DRM_CONTENT);
+        sSupportedContentTypes.add(APP_DRM_MESSAGE);
+
+        // add supported image types
+        sSupportedImageTypes.add(IMAGE_JPEG);
+        sSupportedImageTypes.add(IMAGE_GIF);
+        sSupportedImageTypes.add(IMAGE_WBMP);
+        sSupportedImageTypes.add(IMAGE_PNG);
+        sSupportedImageTypes.add(IMAGE_JPG);
+        sSupportedImageTypes.add(IMAGE_X_MS_BMP);
+
+        // add supported audio types
+        sSupportedAudioTypes.add(AUDIO_AAC);
+        sSupportedAudioTypes.add(AUDIO_AMR);
+        sSupportedAudioTypes.add(AUDIO_IMELODY);
+        sSupportedAudioTypes.add(AUDIO_MID);
+        sSupportedAudioTypes.add(AUDIO_MIDI);
+        sSupportedAudioTypes.add(AUDIO_MP3);
+        sSupportedAudioTypes.add(AUDIO_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_MPEG);
+        sSupportedAudioTypes.add(AUDIO_MPG);
+        sSupportedAudioTypes.add(AUDIO_MP4);
+        sSupportedAudioTypes.add(AUDIO_X_MID);
+        sSupportedAudioTypes.add(AUDIO_X_MIDI);
+        sSupportedAudioTypes.add(AUDIO_X_MP3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG3);
+        sSupportedAudioTypes.add(AUDIO_X_MPEG);
+        sSupportedAudioTypes.add(AUDIO_X_MPG);
+        sSupportedAudioTypes.add(AUDIO_X_WAV);
+        sSupportedAudioTypes.add(AUDIO_3GPP);
+        sSupportedAudioTypes.add(AUDIO_OGG);
+        sSupportedAudioTypes.add(AUDIO_OGG2);
+
+        // add supported video types
+        sSupportedVideoTypes.add(VIDEO_3GPP);
+        sSupportedVideoTypes.add(VIDEO_3G2);
+        sSupportedVideoTypes.add(VIDEO_H263);
+        sSupportedVideoTypes.add(VIDEO_MP4);
+    }
+
+    // This class should never be instantiated.
+    private ContentType() {
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedType(String contentType) {
+        return (null != contentType) && sSupportedContentTypes.contains(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedImageType(String contentType) {
+        return isImageType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedAudioType(String contentType) {
+        return isAudioType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isSupportedVideoType(String contentType) {
+        return isVideoType(contentType) && isSupportedType(contentType);
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isTextType(String contentType) {
+        return (null != contentType) && contentType.startsWith("text/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isImageType(String contentType) {
+        return (null != contentType) && contentType.startsWith("image/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isAudioType(String contentType) {
+        return (null != contentType) && contentType.startsWith("audio/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isVideoType(String contentType) {
+        return (null != contentType) && contentType.startsWith("video/");
+    }
+
+    @UnsupportedAppUsage
+    public static boolean isDrmType(String contentType) {
+        return (null != contentType)
+                && (contentType.equals(APP_DRM_CONTENT)
+                        || contentType.equals(APP_DRM_MESSAGE));
+    }
+
+    public static boolean isUnspecified(String contentType) {
+        return (null != contentType) && contentType.endsWith("*");
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getImageTypes() {
+        return (ArrayList<String>) sSupportedImageTypes.clone();
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getAudioTypes() {
+        return (ArrayList<String>) sSupportedAudioTypes.clone();
+    }
+
+    @UnsupportedAppUsage
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getVideoTypes() {
+        return (ArrayList<String>) sSupportedVideoTypes.clone();
+    }
+
+    @SuppressWarnings("unchecked")
+    public static ArrayList<String> getSupportedTypes() {
+        return (ArrayList<String>) sSupportedContentTypes.clone();
+    }
+}
diff --git a/telephony/java/com/google/android/mms/InvalidHeaderValueException.java b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
new file mode 100644
index 0000000..2836c30
--- /dev/null
+++ b/telephony/java/com/google/android/mms/InvalidHeaderValueException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * Thrown when an invalid header value was set.
+ */
+public class InvalidHeaderValueException extends MmsException {
+    private static final long serialVersionUID = -2053384496042052262L;
+
+    /**
+     * Constructs an InvalidHeaderValueException with no detailed message.
+     */
+    public InvalidHeaderValueException() {
+        super();
+    }
+
+    /**
+     * Constructs an InvalidHeaderValueException with the specified detailed message.
+     *
+     * @param message the detailed message.
+     */
+    @UnsupportedAppUsage
+    public InvalidHeaderValueException(String message) {
+        super(message);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/MmsException.java b/telephony/java/com/google/android/mms/MmsException.java
new file mode 100644
index 0000000..5be33ed
--- /dev/null
+++ b/telephony/java/com/google/android/mms/MmsException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+/**
+ * A generic exception that is thrown by the Mms client.
+ */
+public class MmsException extends Exception {
+    private static final long serialVersionUID = -7323249827281485390L;
+
+    /**
+     * Creates a new MmsException.
+     */
+    @UnsupportedAppUsage
+    public MmsException() {
+        super();
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message.
+     *
+     * @param message the detail message.
+     */
+    @UnsupportedAppUsage
+    public MmsException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new MmsException with the specified cause.
+     *
+     * @param cause the cause.
+     */
+    @UnsupportedAppUsage
+    public MmsException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Creates a new MmsException with the specified detail message and cause.
+     *
+     * @param message the detail message.
+     * @param cause the cause.
+     */
+    @UnsupportedAppUsage
+    public MmsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/package.html b/telephony/java/com/google/android/mms/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
new file mode 100644
index 0000000..ae447d7
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/AcknowledgeInd.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Acknowledge.ind PDU.
+ */
+public class AcknowledgeInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-Acknowledge.ind pdu.
+     *
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     */
+    @UnsupportedAppUsage
+    public AcknowledgeInd(int mmsVersion, byte[] transactionId)
+            throws InvalidHeaderValueException {
+        super();
+
+        setMessageType(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    AcknowledgeInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/Base64.java b/telephony/java/com/google/android/mms/pdu/Base64.java
new file mode 100644
index 0000000..483fa7f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/Base64.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class Base64 {
+    /**
+     * Used to get the number of Quadruples.
+     */
+    static final int FOURBYTE = 4;
+
+    /**
+     * Byte used to pad output.
+     */
+    static final byte PAD = (byte) '=';
+
+    /**
+     * The base length.
+     */
+    static final int BASELENGTH = 255;
+
+    // Create arrays to hold the base64 characters
+    private static byte[] base64Alphabet = new byte[BASELENGTH];
+
+    // Populating the character arrays
+    static {
+        for (int i = 0; i < BASELENGTH; i++) {
+            base64Alphabet[i] = (byte) -1;
+        }
+        for (int i = 'Z'; i >= 'A'; i--) {
+            base64Alphabet[i] = (byte) (i - 'A');
+        }
+        for (int i = 'z'; i >= 'a'; i--) {
+            base64Alphabet[i] = (byte) (i - 'a' + 26);
+        }
+        for (int i = '9'; i >= '0'; i--) {
+            base64Alphabet[i] = (byte) (i - '0' + 52);
+        }
+
+        base64Alphabet['+'] = 62;
+        base64Alphabet['/'] = 63;
+    }
+
+    /**
+     * Decodes Base64 data into octects
+     *
+     * @param base64Data Byte array containing Base64 data
+     * @return Array containing decoded data.
+     */
+    @UnsupportedAppUsage
+    public static byte[] decodeBase64(byte[] base64Data) {
+        // RFC 2045 requires that we discard ALL non-Base64 characters
+        base64Data = discardNonBase64(base64Data);
+
+        // handle the edge case, so we don't have to worry about it later
+        if (base64Data.length == 0) {
+            return new byte[0];
+        }
+
+        int numberQuadruple = base64Data.length / FOURBYTE;
+        byte decodedData[] = null;
+        byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0;
+
+        // Throw away anything not in base64Data
+
+        int encodedIndex = 0;
+        int dataIndex = 0;
+        {
+            // this sizes the output array properly - rlw
+            int lastData = base64Data.length;
+            // ignore the '=' padding
+            while (base64Data[lastData - 1] == PAD) {
+                if (--lastData == 0) {
+                    return new byte[0];
+                }
+            }
+            decodedData = new byte[lastData - numberQuadruple];
+        }
+
+        for (int i = 0; i < numberQuadruple; i++) {
+            dataIndex = i * 4;
+            marker0 = base64Data[dataIndex + 2];
+            marker1 = base64Data[dataIndex + 3];
+
+            b1 = base64Alphabet[base64Data[dataIndex]];
+            b2 = base64Alphabet[base64Data[dataIndex + 1]];
+
+            if (marker0 != PAD && marker1 != PAD) {
+                //No PAD e.g 3cQl
+                b3 = base64Alphabet[marker0];
+                b4 = base64Alphabet[marker1];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+                decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4);
+            } else if (marker0 == PAD) {
+                //Two PAD e.g. 3c[Pad][Pad]
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+            } else if (marker1 == PAD) {
+                //One PAD e.g. 3cQ[Pad]
+                b3 = base64Alphabet[marker0];
+
+                decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+                decodedData[encodedIndex + 1] =
+                    (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+            }
+            encodedIndex += 3;
+        }
+        return decodedData;
+    }
+
+    /**
+     * Check octect whether it is a base64 encoding.
+     *
+     * @param octect to be checked byte
+     * @return ture if it is base64 encoding, false otherwise.
+     */
+    private static boolean isBase64(byte octect) {
+        if (octect == PAD) {
+            return true;
+        } else if (base64Alphabet[octect] == -1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Discards any characters outside of the base64 alphabet, per
+     * the requirements on page 25 of RFC 2045 - "Any characters
+     * outside of the base64 alphabet are to be ignored in base64
+     * encoded data."
+     *
+     * @param data The base-64 encoded data to groom
+     * @return The data, less non-base64 characters (see RFC 2045).
+     */
+    static byte[] discardNonBase64(byte[] data) {
+        byte groomedData[] = new byte[data.length];
+        int bytesCopied = 0;
+
+        for (int i = 0; i < data.length; i++) {
+            if (isBase64(data[i])) {
+                groomedData[bytesCopied++] = data[i];
+            }
+        }
+
+        byte packedData[] = new byte[bytesCopied];
+
+        System.arraycopy(groomedData, 0, packedData, 0, bytesCopied);
+
+        return packedData;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/CharacterSets.java b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
new file mode 100644
index 0000000..27da35e
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/CharacterSets.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
+public class CharacterSets {
+    /**
+     * IANA assigned MIB enum numbers.
+     *
+     * From wap-230-wsp-20010705-a.pdf
+     * Any-charset = <Octet 128>
+     * Equivalent to the special RFC2616 charset value "*"
+     */
+    public static final int ANY_CHARSET = 0x00;
+    public static final int US_ASCII    = 0x03;
+    public static final int ISO_8859_1  = 0x04;
+    public static final int ISO_8859_2  = 0x05;
+    public static final int ISO_8859_3  = 0x06;
+    public static final int ISO_8859_4  = 0x07;
+    public static final int ISO_8859_5  = 0x08;
+    public static final int ISO_8859_6  = 0x09;
+    public static final int ISO_8859_7  = 0x0A;
+    public static final int ISO_8859_8  = 0x0B;
+    public static final int ISO_8859_9  = 0x0C;
+    public static final int SHIFT_JIS   = 0x11;
+    public static final int UTF_8       = 0x6A;
+    public static final int BIG5        = 0x07EA;
+    public static final int UCS2        = 0x03E8;
+    public static final int UTF_16      = 0x03F7;
+
+    /**
+     * If the encoding of given data is unsupported, use UTF_8 to decode it.
+     */
+    public static final int DEFAULT_CHARSET = UTF_8;
+
+    /**
+     * Array of MIB enum numbers.
+     */
+    private static final int[] MIBENUM_NUMBERS = {
+        ANY_CHARSET,
+        US_ASCII,
+        ISO_8859_1,
+        ISO_8859_2,
+        ISO_8859_3,
+        ISO_8859_4,
+        ISO_8859_5,
+        ISO_8859_6,
+        ISO_8859_7,
+        ISO_8859_8,
+        ISO_8859_9,
+        SHIFT_JIS,
+        UTF_8,
+        BIG5,
+        UCS2,
+        UTF_16,
+    };
+
+    /**
+     * The Well-known-charset Mime name.
+     */
+    public static final String MIMENAME_ANY_CHARSET = "*";
+    public static final String MIMENAME_US_ASCII    = "us-ascii";
+    public static final String MIMENAME_ISO_8859_1  = "iso-8859-1";
+    public static final String MIMENAME_ISO_8859_2  = "iso-8859-2";
+    public static final String MIMENAME_ISO_8859_3  = "iso-8859-3";
+    public static final String MIMENAME_ISO_8859_4  = "iso-8859-4";
+    public static final String MIMENAME_ISO_8859_5  = "iso-8859-5";
+    public static final String MIMENAME_ISO_8859_6  = "iso-8859-6";
+    public static final String MIMENAME_ISO_8859_7  = "iso-8859-7";
+    public static final String MIMENAME_ISO_8859_8  = "iso-8859-8";
+    public static final String MIMENAME_ISO_8859_9  = "iso-8859-9";
+    public static final String MIMENAME_SHIFT_JIS   = "shift_JIS";
+    public static final String MIMENAME_UTF_8       = "utf-8";
+    public static final String MIMENAME_BIG5        = "big5";
+    public static final String MIMENAME_UCS2        = "iso-10646-ucs-2";
+    public static final String MIMENAME_UTF_16      = "utf-16";
+
+    public static final String DEFAULT_CHARSET_NAME = MIMENAME_UTF_8;
+
+    /**
+     * Array of the names of character sets.
+     */
+    private static final String[] MIME_NAMES = {
+        MIMENAME_ANY_CHARSET,
+        MIMENAME_US_ASCII,
+        MIMENAME_ISO_8859_1,
+        MIMENAME_ISO_8859_2,
+        MIMENAME_ISO_8859_3,
+        MIMENAME_ISO_8859_4,
+        MIMENAME_ISO_8859_5,
+        MIMENAME_ISO_8859_6,
+        MIMENAME_ISO_8859_7,
+        MIMENAME_ISO_8859_8,
+        MIMENAME_ISO_8859_9,
+        MIMENAME_SHIFT_JIS,
+        MIMENAME_UTF_8,
+        MIMENAME_BIG5,
+        MIMENAME_UCS2,
+        MIMENAME_UTF_16,
+    };
+
+    private static final HashMap<Integer, String> MIBENUM_TO_NAME_MAP;
+    private static final HashMap<String, Integer> NAME_TO_MIBENUM_MAP;
+
+    static {
+        // Create the HashMaps.
+        MIBENUM_TO_NAME_MAP = new HashMap<Integer, String>();
+        NAME_TO_MIBENUM_MAP = new HashMap<String, Integer>();
+        assert(MIBENUM_NUMBERS.length == MIME_NAMES.length);
+        int count = MIBENUM_NUMBERS.length - 1;
+        for(int i = 0; i <= count; i++) {
+            MIBENUM_TO_NAME_MAP.put(MIBENUM_NUMBERS[i], MIME_NAMES[i]);
+            NAME_TO_MIBENUM_MAP.put(MIME_NAMES[i], MIBENUM_NUMBERS[i]);
+        }
+    }
+
+    private CharacterSets() {} // Non-instantiatable
+
+    /**
+     * Map an MIBEnum number to the name of the charset which this number
+     * is assigned to by IANA.
+     *
+     * @param mibEnumValue An IANA assigned MIBEnum number.
+     * @return The name string of the charset.
+     * @throws UnsupportedEncodingException
+     */
+    @UnsupportedAppUsage
+    public static String getMimeName(int mibEnumValue)
+            throws UnsupportedEncodingException {
+        String name = MIBENUM_TO_NAME_MAP.get(mibEnumValue);
+        if (name == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return name;
+    }
+
+    /**
+     * Map a well-known charset name to its assigned MIBEnum number.
+     *
+     * @param mimeName The charset name.
+     * @return The MIBEnum number assigned by IANA for this charset.
+     * @throws UnsupportedEncodingException
+     */
+    @UnsupportedAppUsage
+    public static int getMibEnumValue(String mimeName)
+            throws UnsupportedEncodingException {
+        if(null == mimeName) {
+            return -1;
+        }
+
+        Integer mibEnumValue = NAME_TO_MIBENUM_MAP.get(mimeName);
+        if (mibEnumValue == null) {
+            throw new UnsupportedEncodingException();
+        }
+        return mibEnumValue;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/DeliveryInd.java b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
new file mode 100644
index 0000000..7093ac6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/DeliveryInd.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Delivery.Ind Pdu.
+ */
+public class DeliveryInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public DeliveryInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_DELIVERY_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    DeliveryInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value, should not be null
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get Status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Set Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public EncodedStringValue getStatusText() {return null;}
+     *     public void setStatusText(EncodedStringValue value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
new file mode 100644
index 0000000..4166275
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/EncodedStringValue.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ */
+public class EncodedStringValue implements Cloneable {
+    private static final String TAG = "EncodedStringValue";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    /**
+     * The Char-set value.
+     */
+    private int mCharacterSet;
+
+    /**
+     * The Text-string value.
+     */
+    private byte[] mData;
+
+    /**
+     * Constructor.
+     *
+     * @param charset the Char-set value
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue(int charset, byte[] data) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        if(null == data) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mCharacterSet = charset;
+        mData = new byte[data.length];
+        System.arraycopy(data, 0, mData, 0, data.length);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param data the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue(byte[] data) {
+        this(CharacterSets.DEFAULT_CHARSET, data);
+    }
+
+    @UnsupportedAppUsage
+    public EncodedStringValue(String data) {
+        try {
+            mData = data.getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+            mCharacterSet = CharacterSets.DEFAULT_CHARSET;
+        } catch (UnsupportedEncodingException e) {
+            Log.e(TAG, "Default encoding must be supported.", e);
+        }
+    }
+
+    /**
+     * Get Char-set value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getCharacterSet() {
+        return mCharacterSet;
+    }
+
+    /**
+     * Set Char-set value.
+     *
+     * @param charset the Char-set value
+     */
+    @UnsupportedAppUsage
+    public void setCharacterSet(int charset) {
+        // TODO: CharSet needs to be validated against MIBEnum.
+        mCharacterSet = charset;
+    }
+
+    /**
+     * Get Text-string value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTextString() {
+        byte[] byteArray = new byte[mData.length];
+
+        System.arraycopy(mData, 0, byteArray, 0, mData.length);
+        return byteArray;
+    }
+
+    /**
+     * Set Text-string value.
+     *
+     * @param textString the Text-string value
+     * @throws NullPointerException if Text-string value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("EncodedStringValue: Text-string is null.");
+        }
+
+        mData = new byte[textString.length];
+        System.arraycopy(textString, 0, mData, 0, textString.length);
+    }
+
+    /**
+     * Convert this object to a {@link java.lang.String}. If the encoding of
+     * the EncodedStringValue is null or unsupported, it will be
+     * treated as iso-8859-1 encoding.
+     *
+     * @return The decoded String.
+     */
+    @UnsupportedAppUsage
+    public String getString()  {
+        if (CharacterSets.ANY_CHARSET == mCharacterSet) {
+            return new String(mData); // system default encoding.
+        } else {
+            try {
+                String name = CharacterSets.getMimeName(mCharacterSet);
+                return new String(mData, name);
+            } catch (UnsupportedEncodingException e) {
+            	if (LOCAL_LOGV) {
+            		Log.v(TAG, e.getMessage(), e);
+            	}
+            	try {
+                    return new String(mData, CharacterSets.MIMENAME_ISO_8859_1);
+                } catch (UnsupportedEncodingException e2) {
+                    return new String(mData); // system default encoding.
+                }
+            }
+        }
+    }
+
+    /**
+     * Append to Text-string.
+     *
+     * @param textString the textString to append
+     * @throws NullPointerException if the text String is null
+     *                      or an IOException occurred.
+     */
+    @UnsupportedAppUsage
+    public void appendTextString(byte[] textString) {
+        if(null == textString) {
+            throw new NullPointerException("Text-string is null.");
+        }
+
+        if(null == mData) {
+            mData = new byte[textString.length];
+            System.arraycopy(textString, 0, mData, 0, textString.length);
+        } else {
+            ByteArrayOutputStream newTextString = new ByteArrayOutputStream();
+            try {
+                newTextString.write(mData);
+                newTextString.write(textString);
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new NullPointerException(
+                        "appendTextString: failed when write a new Text-string");
+            }
+
+            mData = newTextString.toByteArray();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see java.lang.Object#clone()
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        super.clone();
+        int len = mData.length;
+        byte[] dstBytes = new byte[len];
+        System.arraycopy(mData, 0, dstBytes, 0, len);
+
+        try {
+            return new EncodedStringValue(mCharacterSet, dstBytes);
+        } catch (Exception e) {
+            Log.e(TAG, "failed to clone an EncodedStringValue: " + this);
+            e.printStackTrace();
+            throw new CloneNotSupportedException(e.getMessage());
+        }
+    }
+
+    /**
+     * Split this encoded string around matches of the given pattern.
+     *
+     * @param pattern the delimiting pattern
+     * @return the array of encoded strings computed by splitting this encoded
+     *         string around matches of the given pattern
+     */
+    public EncodedStringValue[] split(String pattern) {
+        String[] temp = getString().split(pattern);
+        EncodedStringValue[] ret = new EncodedStringValue[temp.length];
+        for (int i = 0; i < ret.length; ++i) {
+            try {
+                ret[i] = new EncodedStringValue(mCharacterSet,
+                        temp[i].getBytes());
+            } catch (NullPointerException e) {
+                // Can't arrive here
+                return null;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Extract an EncodedStringValue[] from a given String.
+     */
+    @UnsupportedAppUsage
+    public static EncodedStringValue[] extract(String src) {
+        String[] values = src.split(";");
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < values.length; i++) {
+            if (values[i].length() > 0) {
+                list.add(new EncodedStringValue(values[i]));
+            }
+        }
+
+        int len = list.size();
+        if (len > 0) {
+            return list.toArray(new EncodedStringValue[len]);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Concatenate an EncodedStringValue[] into a single String.
+     */
+    @UnsupportedAppUsage
+    public static String concat(EncodedStringValue[] addr) {
+        StringBuilder sb = new StringBuilder();
+        int maxIndex = addr.length - 1;
+        for (int i = 0; i <= maxIndex; i++) {
+            sb.append(addr[i].getString());
+            if (i < maxIndex) {
+                sb.append(";");
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @UnsupportedAppUsage
+    public static EncodedStringValue copy(EncodedStringValue value) {
+        if (value == null) {
+            return null;
+        }
+
+        return new EncodedStringValue(value.mCharacterSet, value.mData);
+    }
+    
+    @UnsupportedAppUsage
+    public static EncodedStringValue[] encodeStrings(String[] array) {
+        int count = array.length;
+        if (count > 0) {
+            EncodedStringValue[] encodedArray = new EncodedStringValue[count];
+            for (int i = 0; i < count; i++) {
+                encodedArray[i] = new EncodedStringValue(array[i]);
+            }
+            return encodedArray;
+        }
+        return null;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/GenericPdu.java b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
new file mode 100644
index 0000000..ebf16ac
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/GenericPdu.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class GenericPdu {
+    /**
+     * The headers of pdu.
+     */
+    @UnsupportedAppUsage
+    PduHeaders mPduHeaders = null;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu() {
+        mPduHeaders = new PduHeaders();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param headers Headers for this PDU.
+     */
+    GenericPdu(PduHeaders headers) {
+        mPduHeaders = headers;
+    }
+
+    /**
+     * Get the headers of this PDU.
+     *
+     * @return A PduHeaders of this PDU.
+     */
+    @UnsupportedAppUsage
+    PduHeaders getPduHeaders() {
+        return mPduHeaders;
+    }
+
+    /**
+     * Get X-Mms-Message-Type field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public int getMessageType() {
+        return mPduHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Set X-Mms-Message-Type field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    @UnsupportedAppUsage
+    public void setMessageType(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MESSAGE_TYPE);
+    }
+
+    /**
+     * Get X-Mms-MMS-Version field value.
+     *
+     * @return the X-Mms-MMS-Version value
+     */
+    public int getMmsVersion() {
+        return mPduHeaders.getOctet(PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Set X-Mms-MMS-Version field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if field's value is not Octet.
+     */
+    public void setMmsVersion(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.MMS_VERSION);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
new file mode 100644
index 0000000..e108f76
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/MultimediaMessagePdu.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * Multimedia message PDU.
+ */
+public class MultimediaMessagePdu extends GenericPdu{
+    /**
+     * The body.
+     */
+    private PduBody mMessageBody;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public MultimediaMessagePdu() {
+        super();
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param header the header of this PDU
+     * @param body the body of this PDU
+     */
+    @UnsupportedAppUsage
+    public MultimediaMessagePdu(PduHeaders header, PduBody body) {
+        super(header);
+        mMessageBody = body;
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    MultimediaMessagePdu(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get body of the PDU.
+     *
+     * @return the body
+     */
+    @UnsupportedAppUsage
+    public PduBody getBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * Set body of the PDU.
+     *
+     * @param body the body
+     */
+    @UnsupportedAppUsage
+    public void setBody(PduBody body) {
+        mMessageBody = body;
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Add a "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addTo(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Priority value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getPriority() {
+        return mPduHeaders.getOctet(PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Set X-Mms-Priority value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setPriority(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.PRIORITY);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value in seconds.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotificationInd.java b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
new file mode 100644
index 0000000..b561bd4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotificationInd.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Notification.ind PDU.
+ */
+public class NotificationInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public NotificationInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    NotificationInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Content-Class Value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getContentClass() {
+        return mPduHeaders.getOctet(PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Content-Class Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setContentClass(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.CONTENT_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Content-Location value.
+     * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+     * Content-location-value = Uri-value
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentLocation() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Set X-Mms-Content-Location value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setContentLocation(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_LOCATION);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Message-Size value.
+     * Message-size-value = Long-integer
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-Message-Size value.
+     *
+     * @param value the value
+     * @throws RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get subject.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getSubject() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Set subject.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setSubject(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.SUBJECT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report Value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report Value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public ElementDescriptorValue getElementDescriptor() {return null;}
+     *     public void getElementDescriptor(ElementDescriptorValue value) {}
+     *
+     *     public byte getPriority() {return 0x00;}
+     *     public void setPriority(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalMode() {return 0x00;}
+     *     public void setRecommendedRetrievalMode(byte value) {}
+     *
+     *     public byte getRecommendedRetrievalModeText() {return 0x00;}
+     *     public void setRecommendedRetrievalModeText(byte value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte getStored() {return 0x00;}
+     *     public void setStored(byte value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
new file mode 100644
index 0000000..3c70f86
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/NotifyRespInd.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-NofifyResp.ind PDU.
+ */
+public class NotifyRespInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-NotifyResp.ind pdu.
+     *
+     * @param mmsVersion current version of mms
+     * @param transactionId the transaction-id value
+     * @param status the status value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if transactionId is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public NotifyRespInd(int mmsVersion,
+                         byte[] transactionId,
+                         int status) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+        setStatus(status);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    NotifyRespInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get X-Mms-Report-Allowed field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public int getReportAllowed() {
+        return mPduHeaders.getOctet(PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Report-Allowed field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setReportAllowed(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.REPORT_ALLOWED);
+    }
+
+    /**
+     * Set X-Mms-Status field value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.STATUS);
+    }
+
+    /**
+     * GetX-Mms-Status field value.
+     *
+     * @return the X-Mms-Status value
+     */
+    public int getStatus() {
+        return mPduHeaders.getOctet(PduHeaders.STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     *         RuntimeException if an undeclared error occurs.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduBody.java b/telephony/java/com/google/android/mms/pdu/PduBody.java
new file mode 100644
index 0000000..51914e4
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduBody.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+public class PduBody {
+    private Vector<PduPart> mParts = null;
+
+    private Map<String, PduPart> mPartMapByContentId = null;
+    private Map<String, PduPart> mPartMapByContentLocation = null;
+    private Map<String, PduPart> mPartMapByName = null;
+    private Map<String, PduPart> mPartMapByFileName = null;
+
+    /**
+     * Constructor.
+     */
+    @UnsupportedAppUsage
+    public PduBody() {
+        mParts = new Vector<PduPart>();
+
+        mPartMapByContentId = new HashMap<String, PduPart>();
+        mPartMapByContentLocation  = new HashMap<String, PduPart>();
+        mPartMapByName = new HashMap<String, PduPart>();
+        mPartMapByFileName = new HashMap<String, PduPart>();
+    }
+
+    private void putPartToMaps(PduPart part) {
+        // Put part to mPartMapByContentId.
+        byte[] contentId = part.getContentId();
+        if(null != contentId) {
+            mPartMapByContentId.put(new String(contentId), part);
+        }
+
+        // Put part to mPartMapByContentLocation.
+        byte[] contentLocation = part.getContentLocation();
+        if(null != contentLocation) {
+            String clc = new String(contentLocation);
+            mPartMapByContentLocation.put(clc, part);
+        }
+
+        // Put part to mPartMapByName.
+        byte[] name = part.getName();
+        if(null != name) {
+            String clc = new String(name);
+            mPartMapByName.put(clc, part);
+        }
+
+        // Put part to mPartMapByFileName.
+        byte[] fileName = part.getFilename();
+        if(null != fileName) {
+            String clc = new String(fileName);
+            mPartMapByFileName.put(clc, part);
+        }
+    }
+
+    /**
+     * Appends the specified part to the end of this body.
+     *
+     * @param part part to be appended
+     * @return true when success, false when fail
+     * @throws NullPointerException when part is null
+     */
+    @UnsupportedAppUsage
+    public boolean addPart(PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        return mParts.add(part);
+    }
+
+    /**
+     * Inserts the specified part at the specified position.
+     *
+     * @param index index at which the specified part is to be inserted
+     * @param part part to be inserted
+     * @throws NullPointerException when part is null
+     */
+    @UnsupportedAppUsage
+    public void addPart(int index, PduPart part) {
+        if(null == part) {
+            throw new NullPointerException();
+        }
+
+        putPartToMaps(part);
+        mParts.add(index, part);
+    }
+
+    /**
+     * Removes the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    @UnsupportedAppUsage
+    public PduPart removePart(int index) {
+        return mParts.remove(index);
+    }
+
+    /**
+     * Remove all of the parts.
+     */
+    public void removeAll() {
+        mParts.clear();
+    }
+
+    /**
+     * Get the part at the specified position.
+     *
+     * @param index index of the part to return
+     * @return part at the specified index
+     */
+    @UnsupportedAppUsage
+    public PduPart getPart(int index) {
+        return mParts.get(index);
+    }
+
+    /**
+     * Get the index of the specified part.
+     *
+     * @param part the part object
+     * @return index the index of the first occurrence of the part in this body
+     */
+    @UnsupportedAppUsage
+    public int getPartIndex(PduPart part) {
+        return mParts.indexOf(part);
+    }
+
+    /**
+     * Get the number of parts.
+     *
+     * @return the number of parts
+     */
+    @UnsupportedAppUsage
+    public int getPartsNum() {
+        return mParts.size();
+    }
+
+    /**
+     * Get pdu part by content id.
+     *
+     * @param cid the value of content id.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByContentId(String cid) {
+        return mPartMapByContentId.get(cid);
+    }
+
+    /**
+     * Get pdu part by Content-Location. Content-Location of part is
+     * the same as filename and name(param of content-type).
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByContentLocation(String contentLocation) {
+        return mPartMapByContentLocation.get(contentLocation);
+    }
+
+    /**
+     * Get pdu part by name.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByName(String name) {
+        return mPartMapByName.get(name);
+    }
+
+    /**
+     * Get pdu part by filename.
+     *
+     * @param fileName the value of filename.
+     * @return the pdu part.
+     */
+    @UnsupportedAppUsage
+    public PduPart getPartByFileName(String filename) {
+        return mPartMapByFileName.get(filename);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduComposer.java b/telephony/java/com/google/android/mms/pdu/PduComposer.java
new file mode 100644
index 0000000..e24bf21
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduComposer.java
@@ -0,0 +1,1229 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.text.TextUtils;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduComposer {
+    /**
+     * Address type.
+     */
+    static private final int PDU_PHONE_NUMBER_ADDRESS_TYPE = 1;
+    static private final int PDU_EMAIL_ADDRESS_TYPE = 2;
+    static private final int PDU_IPV4_ADDRESS_TYPE = 3;
+    static private final int PDU_IPV6_ADDRESS_TYPE = 4;
+    static private final int PDU_UNKNOWN_ADDRESS_TYPE = 5;
+
+    /**
+     * Address regular expression string.
+     */
+    static final String REGEXP_PHONE_NUMBER_ADDRESS_TYPE = "\\+?[0-9|\\.|\\-]+";
+    static final String REGEXP_EMAIL_ADDRESS_TYPE = "[a-zA-Z| ]*\\<{0,1}[a-zA-Z| ]+@{1}" +
+            "[a-zA-Z| ]+\\.{1}[a-zA-Z| ]+\\>{0,1}";
+    static final String REGEXP_IPV6_ADDRESS_TYPE =
+        "[a-fA-F]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}\\:{1}" +
+        "[a-fA-F0-9]{4}\\:{1}[a-fA-F0-9]{4}";
+    static final String REGEXP_IPV4_ADDRESS_TYPE = "[0-9]{1,3}\\.{1}[0-9]{1,3}\\.{1}" +
+            "[0-9]{1,3}\\.{1}[0-9]{1,3}";
+
+    /**
+     * The postfix strings of address.
+     */
+    static final String STRING_PHONE_NUMBER_ADDRESS_TYPE = "/TYPE=PLMN";
+    static final String STRING_IPV4_ADDRESS_TYPE = "/TYPE=IPV4";
+    static final String STRING_IPV6_ADDRESS_TYPE = "/TYPE=IPV6";
+
+    /**
+     * Error values.
+     */
+    static private final int PDU_COMPOSE_SUCCESS = 0;
+    static private final int PDU_COMPOSE_CONTENT_ERROR = 1;
+    static private final int PDU_COMPOSE_FIELD_NOT_SET = 2;
+    static private final int PDU_COMPOSE_FIELD_NOT_SUPPORTED = 3;
+
+    /**
+     * WAP values defined in WSP spec.
+     */
+    static private final int QUOTED_STRING_FLAG = 34;
+    static private final int END_STRING_FLAG = 0;
+    static private final int LENGTH_QUOTE = 31;
+    static private final int TEXT_MAX = 127;
+    static private final int SHORT_INTEGER_MAX = 127;
+    static private final int LONG_INTEGER_LENGTH_MAX = 8;
+
+    /**
+     * Block size when read data from InputStream.
+     */
+    static private final int PDU_COMPOSER_BLOCK_SIZE = 1024;
+
+    /**
+     * The output message.
+     */
+    @UnsupportedAppUsage
+    protected ByteArrayOutputStream mMessage = null;
+
+    /**
+     * The PDU.
+     */
+    @UnsupportedAppUsage
+    private GenericPdu mPdu = null;
+
+    /**
+     * Current visiting position of the mMessage.
+     */
+    @UnsupportedAppUsage
+    protected int mPosition = 0;
+
+    /**
+     * Message compose buffer stack.
+     */
+    @UnsupportedAppUsage
+    private BufferStack mStack = null;
+
+    /**
+     * Content resolver.
+     */
+    @UnsupportedAppUsage
+    private final ContentResolver mResolver;
+
+    /**
+     * Header of this pdu.
+     */
+    @UnsupportedAppUsage
+    private PduHeaders mPduHeader = null;
+
+    /**
+     * Map of all content type
+     */
+    @UnsupportedAppUsage
+    private static HashMap<String, Integer> mContentTypeMap = null;
+
+    static {
+        mContentTypeMap = new HashMap<String, Integer>();
+
+        int i;
+        for (i = 0; i < PduContentTypes.contentTypes.length; i++) {
+            mContentTypeMap.put(PduContentTypes.contentTypes[i], i);
+        }
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context the context
+     * @param pdu the pdu to be composed
+     */
+    @UnsupportedAppUsage
+    public PduComposer(Context context, GenericPdu pdu) {
+        mPdu = pdu;
+        mResolver = context.getContentResolver();
+        mPduHeader = pdu.getPduHeaders();
+        mStack = new BufferStack();
+        mMessage = new ByteArrayOutputStream();
+        mPosition = 0;
+    }
+
+    /**
+     * Make the message. No need to check whether mandatory fields are set,
+     * because the constructors of outgoing pdus are taking care of this.
+     *
+     * @return OutputStream of maked message. Return null if
+     *         the PDU is invalid.
+     */
+    @UnsupportedAppUsage
+    public byte[] make() {
+        // Get Message-type.
+        int type = mPdu.getMessageType();
+
+        /* make the message */
+        switch (type) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                if (makeSendRetrievePdu(type) != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (makeNotifyResp() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (makeAckInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (makeReadRecInd() != PDU_COMPOSE_SUCCESS) {
+                    return null;
+                }
+                break;
+            default:
+                return null;
+        }
+
+        return mMessage.toByteArray();
+    }
+
+    /**
+     *  Copy buf to mMessage.
+     */
+    @UnsupportedAppUsage
+    protected void arraycopy(byte[] buf, int pos, int length) {
+        mMessage.write(buf, pos, length);
+        mPosition = mPosition + length;
+    }
+
+    /**
+     * Append a byte to mMessage.
+     */
+    protected void append(int value) {
+        mMessage.write(value);
+        mPosition ++;
+    }
+
+    /**
+     * Append short integer value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendShortInteger(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-integer = OCTET
+         * ; Integers in range 0-127 shall be encoded as a one octet value
+         * ; with the most significant bit set to one (1xxx xxxx) and with
+         * ; the value in the remaining least significant bits.
+         * In our implementation, only low 7 bits are stored and otherwise
+         * bits are ignored.
+         */
+        append((value | 0x80) & 0xff);
+    }
+
+    /**
+     * Append an octet number between 128 and 255 into mMessage.
+     * NOTE:
+     * A value between 0 and 127 should be appended by using appendShortInteger.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendOctet(int number) {
+        append(number);
+    }
+
+    /**
+     * Append a short length into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendShortLength(int value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Short-length = <Any octet 0-30>
+         */
+        append(value);
+    }
+
+    /**
+     * Append long integer into mMessage. it's used for really long integers.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendLongInteger(long longInt) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Long-integer = Short-length Multi-octet-integer
+         * ; The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * ; The content octets shall be an unsigned integer value with the
+         * ; most significant octet encoded first (big-endian representation).
+         * ; The minimum number of octets must be used to encode the value.
+         */
+        int size;
+        long temp = longInt;
+
+        // Count the length of the long integer.
+        for(size = 0; (temp != 0) && (size < LONG_INTEGER_LENGTH_MAX); size++) {
+            temp = (temp >>> 8);
+        }
+
+        // Set Length.
+        appendShortLength(size);
+
+        // Count and set the long integer.
+        int i;
+        int shift = (size -1) * 8;
+
+        for (i = 0; i < size; i++) {
+            append((int)((longInt >>> shift) & 0xff));
+            shift = shift - 8;
+        }
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendTextString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        if (((text[0])&0xff) > TEXT_MAX) { // No need to check for <= 255
+            append(TEXT_MAX);
+        }
+
+        arraycopy(text, 0, text.length);
+        append(0);
+    }
+
+    /**
+     * Append text string into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendTextString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Text-string = [Quote] *TEXT End-of-string
+         * ; If the first character in the TEXT is in the range of 128-255,
+         * ; a Quote character must precede it. Otherwise the Quote character
+         * ;must be omitted. The Quote is not part of the contents.
+         */
+        appendTextString(str.getBytes());
+    }
+
+    /**
+     * Append encoded string value to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendEncodedString(EncodedStringValue enStr) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(enStr != null);
+
+        int charset = enStr.getCharacterSet();
+        byte[] textString = enStr.getTextString();
+        if (null == textString) {
+            return;
+        }
+
+        /*
+         * In the implementation of EncodedStringValue, the charset field will
+         * never be 0. It will always be composed as
+         * Encoded-string-value = Value-length Char-set Text-string
+         */
+        mStack.newbuf();
+        PositionMarker start = mStack.mark();
+
+        appendShortInteger(charset);
+        appendTextString(textString);
+
+        int len = start.getLength();
+        mStack.pop();
+        appendValueLength(len);
+        mStack.copy();
+    }
+
+    /**
+     * Append uintvar integer into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendUintvarInteger(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * To encode a large unsigned integer, split it into 7-bit fragments
+         * and place them in the payloads of multiple octets. The most significant
+         * bits are placed in the first octets with the least significant bits
+         * ending up in the last octet. All octets MUST set the Continue bit to 1
+         * except the last octet, which MUST set the Continue bit to 0.
+         */
+        int i;
+        long max = SHORT_INTEGER_MAX;
+
+        for (i = 0; i < 5; i++) {
+            if (value < max) {
+                break;
+            }
+
+            max = (max << 7) | 0x7fl;
+        }
+
+        while(i > 0) {
+            long temp = value >>> (i * 7);
+            temp = temp & 0x7f;
+
+            append((int)((temp | 0x80) & 0xff));
+
+            i--;
+        }
+
+        append((int)(value & 0x7f));
+    }
+
+    /**
+     * Append date value into mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    protected void appendDateValue(long date) {
+        /*
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C:
+         * Date-value = Long-integer
+         */
+        appendLongInteger(date);
+    }
+
+    /**
+     * Append value length to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendValueLength(long value) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Value-length = Short-length | (Length-quote Length)
+         * ; Value length is used to indicate the length of the value to follow
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         */
+        if (value < LENGTH_QUOTE) {
+            appendShortLength((int) value);
+            return;
+        }
+
+        append(LENGTH_QUOTE);
+        appendUintvarInteger(value);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendQuotedString(byte[] text) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        append(QUOTED_STRING_FLAG);
+        arraycopy(text, 0, text.length);
+        append(END_STRING_FLAG);
+    }
+
+    /**
+     * Append quoted string to mMessage.
+     * This implementation doesn't check the validity of parameter, since it
+     * assumes that the values are validated in the GenericPdu setter methods.
+     */
+    @UnsupportedAppUsage
+    protected void appendQuotedString(String str) {
+        /*
+         * From WAP-230-WSP-20010705-a:
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         * ;The TEXT encodes an RFC2616 Quoted-string with the enclosing
+         * ;quotation-marks <"> removed.
+         */
+        appendQuotedString(str.getBytes());
+    }
+
+    private EncodedStringValue appendAddressType(EncodedStringValue address) {
+        EncodedStringValue temp = null;
+
+        try {
+            int addressType = checkAddressType(address.getString());
+            temp = EncodedStringValue.copy(address);
+            if (PDU_PHONE_NUMBER_ADDRESS_TYPE == addressType) {
+                // Phone number.
+                temp.appendTextString(STRING_PHONE_NUMBER_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV4_ADDRESS_TYPE == addressType) {
+                // Ipv4 address.
+                temp.appendTextString(STRING_IPV4_ADDRESS_TYPE.getBytes());
+            } else if (PDU_IPV6_ADDRESS_TYPE == addressType) {
+                // Ipv6 address.
+                temp.appendTextString(STRING_IPV6_ADDRESS_TYPE.getBytes());
+            }
+        } catch (NullPointerException e) {
+            return null;
+        }
+
+        return temp;
+    }
+
+    /**
+     * Append header to mMessage.
+     */
+    @UnsupportedAppUsage
+    private int appendHeader(int field) {
+        switch (field) {
+            case PduHeaders.MMS_VERSION:
+                appendOctet(field);
+
+                int version = mPduHeader.getOctet(field);
+                if (0 == version) {
+                    appendShortInteger(PduHeaders.CURRENT_MMS_VERSION);
+                } else {
+                    appendShortInteger(version);
+                }
+
+                break;
+
+            case PduHeaders.MESSAGE_ID:
+            case PduHeaders.TRANSACTION_ID:
+                byte[] textString = mPduHeader.getTextString(field);
+                if (null == textString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendTextString(textString);
+                break;
+
+            case PduHeaders.TO:
+            case PduHeaders.BCC:
+            case PduHeaders.CC:
+                EncodedStringValue[] addr = mPduHeader.getEncodedStringValues(field);
+
+                if (null == addr) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                EncodedStringValue temp;
+                for (int i = 0; i < addr.length; i++) {
+                    temp = appendAddressType(addr[i]);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendOctet(field);
+                    appendEncodedString(temp);
+                }
+                break;
+
+            case PduHeaders.FROM:
+                // Value-length (Address-present-token Encoded-string-value | Insert-address-token)
+                appendOctet(field);
+
+                EncodedStringValue from = mPduHeader.getEncodedStringValue(field);
+                if ((from == null)
+                        || TextUtils.isEmpty(from.getString())
+                        || new String(from.getTextString()).equals(
+                                PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+                    // Length of from = 1
+                    append(1);
+                    // Insert-address-token = <Octet 129>
+                    append(PduHeaders.FROM_INSERT_ADDRESS_TOKEN);
+                } else {
+                    mStack.newbuf();
+                    PositionMarker fstart = mStack.mark();
+
+                    // Address-present-token = <Octet 128>
+                    append(PduHeaders.FROM_ADDRESS_PRESENT_TOKEN);
+
+                    temp = appendAddressType(from);
+                    if (temp == null) {
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+
+                    appendEncodedString(temp);
+
+                    int flen = fstart.getLength();
+                    mStack.pop();
+                    appendValueLength(flen);
+                    mStack.copy();
+                }
+                break;
+
+            case PduHeaders.READ_STATUS:
+            case PduHeaders.STATUS:
+            case PduHeaders.REPORT_ALLOWED:
+            case PduHeaders.PRIORITY:
+            case PduHeaders.DELIVERY_REPORT:
+            case PduHeaders.READ_REPORT:
+            case PduHeaders.RETRIEVE_STATUS:
+                int octet = mPduHeader.getOctet(field);
+                if (0 == octet) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendOctet(octet);
+                break;
+
+            case PduHeaders.DATE:
+                long date = mPduHeader.getLongInteger(field);
+                if (-1 == date) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendDateValue(date);
+                break;
+
+            case PduHeaders.SUBJECT:
+            case PduHeaders.RETRIEVE_TEXT:
+                EncodedStringValue enString =
+                    mPduHeader.getEncodedStringValue(field);
+                if (null == enString) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                appendEncodedString(enString);
+                break;
+
+            case PduHeaders.MESSAGE_CLASS:
+                byte[] messageClass = mPduHeader.getTextString(field);
+                if (null == messageClass) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+                if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_ADVERTISEMENT);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_AUTO);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_PERSONAL);
+                } else if (Arrays.equals(messageClass,
+                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes())) {
+                    appendOctet(PduHeaders.MESSAGE_CLASS_INFORMATIONAL);
+                } else {
+                    appendTextString(messageClass);
+                }
+                break;
+
+            case PduHeaders.EXPIRY:
+                long expiry = mPduHeader.getLongInteger(field);
+                if (-1 == expiry) {
+                    return PDU_COMPOSE_FIELD_NOT_SET;
+                }
+
+                appendOctet(field);
+
+                mStack.newbuf();
+                PositionMarker expiryStart = mStack.mark();
+
+                append(PduHeaders.VALUE_RELATIVE_TOKEN);
+                appendLongInteger(expiry);
+
+                int expiryLength = expiryStart.getLength();
+                mStack.pop();
+                appendValueLength(expiryLength);
+                mStack.copy();
+                break;
+
+            default:
+                return PDU_COMPOSE_FIELD_NOT_SUPPORTED;
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make ReadRec.Ind.
+     */
+    private int makeReadRecInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Message-ID
+        if (appendHeader(PduHeaders.MESSAGE_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Optional
+        appendHeader(PduHeaders.DATE);
+
+        // X-Mms-Read-Status
+        if (appendHeader(PduHeaders.READ_STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Applic-ID Optional(not support)
+        // X-Mms-Reply-Applic-ID Optional(not support)
+        // X-Mms-Aux-Applic-Info Optional(not support)
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make NotifyResp.Ind.
+     */
+    private int makeNotifyResp() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //  X-Mms-Status
+        if (appendHeader(PduHeaders.STATUS) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional (not support)
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Acknowledge.Ind.
+     */
+    private int makeAckInd() {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        //    X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND);
+
+        // X-Mms-Transaction-ID
+        if (appendHeader(PduHeaders.TRANSACTION_ID) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        //     X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // X-Mms-Report-Allowed Optional
+        appendHeader(PduHeaders.REPORT_ALLOWED);
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     * Make Send.req.
+     */
+    private int makeSendRetrievePdu(int type) {
+        if (mMessage == null) {
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        // X-Mms-Message-Type
+        appendOctet(PduHeaders.MESSAGE_TYPE);
+        appendOctet(type);
+
+        // X-Mms-Transaction-ID
+        appendOctet(PduHeaders.TRANSACTION_ID);
+
+        byte[] trid = mPduHeader.getTextString(PduHeaders.TRANSACTION_ID);
+        if (trid == null) {
+            // Transaction-ID should be set(by Transaction) before make().
+            throw new IllegalArgumentException("Transaction-ID is null.");
+        }
+        appendTextString(trid);
+
+        //  X-Mms-MMS-Version
+        if (appendHeader(PduHeaders.MMS_VERSION) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Date Date-value Optional.
+        appendHeader(PduHeaders.DATE);
+
+        // From
+        if (appendHeader(PduHeaders.FROM) != PDU_COMPOSE_SUCCESS) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        boolean recipient = false;
+
+        // To
+        if (appendHeader(PduHeaders.TO) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Cc
+        if (appendHeader(PduHeaders.CC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Bcc
+        if (appendHeader(PduHeaders.BCC) != PDU_COMPOSE_CONTENT_ERROR) {
+            recipient = true;
+        }
+
+        // Need at least one of "cc", "bcc" and "to".
+        if (false == recipient) {
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        // Subject Optional
+        appendHeader(PduHeaders.SUBJECT);
+
+        // X-Mms-Message-Class Optional
+        // Message-class-value = Class-identifier | Token-text
+        appendHeader(PduHeaders.MESSAGE_CLASS);
+
+        // X-Mms-Expiry Optional
+        appendHeader(PduHeaders.EXPIRY);
+
+        // X-Mms-Priority Optional
+        appendHeader(PduHeaders.PRIORITY);
+
+        // X-Mms-Delivery-Report Optional
+        appendHeader(PduHeaders.DELIVERY_REPORT);
+
+        // X-Mms-Read-Report Optional
+        appendHeader(PduHeaders.READ_REPORT);
+
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            // X-Mms-Retrieve-Status Optional
+            appendHeader(PduHeaders.RETRIEVE_STATUS);
+            // X-Mms-Retrieve-Text Optional
+            appendHeader(PduHeaders.RETRIEVE_TEXT);
+        }
+
+
+        //    Content-Type
+        appendOctet(PduHeaders.CONTENT_TYPE);
+
+        //  Message body
+        return makeMessageBody(type);
+    }
+
+    /**
+     * Make message body.
+     */
+    private int makeMessageBody(int type) {
+        // 1. add body informations
+        mStack.newbuf();  // Switching buffer because we need to
+
+        PositionMarker ctStart = mStack.mark();
+
+        // This contentTypeIdentifier should be used for type of attachment...
+        String contentType = new String(mPduHeader.getTextString(PduHeaders.CONTENT_TYPE));
+        Integer contentTypeIdentifier = mContentTypeMap.get(contentType);
+        if (contentTypeIdentifier == null) {
+            // content type is mandatory
+            return PDU_COMPOSE_CONTENT_ERROR;
+        }
+
+        appendShortInteger(contentTypeIdentifier.intValue());
+
+        // content-type parameter: start
+        PduBody body;
+        if (type == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) {
+            body = ((RetrieveConf) mPdu).getBody();
+        } else {
+            body = ((SendReq) mPdu).getBody();
+        }
+        if (null == body || body.getPartsNum() == 0) {
+            // empty message
+            appendUintvarInteger(0);
+            mStack.pop();
+            mStack.copy();
+            return PDU_COMPOSE_SUCCESS;
+        }
+
+        PduPart part;
+        try {
+            part = body.getPart(0);
+
+            byte[] start = part.getContentId();
+            if (start != null) {
+                appendOctet(PduPart.P_DEP_START);
+                if (('<' == start[0]) && ('>' == start[start.length - 1])) {
+                    appendTextString(start);
+                } else {
+                    appendTextString("<" + new String(start) + ">");
+                }
+            }
+
+            // content-type parameter: type
+            appendOctet(PduPart.P_CT_MR_TYPE);
+            appendTextString(part.getContentType());
+        }
+        catch (ArrayIndexOutOfBoundsException e){
+            e.printStackTrace();
+        }
+
+        int ctLength = ctStart.getLength();
+        mStack.pop();
+        appendValueLength(ctLength);
+        mStack.copy();
+
+        // 3. add content
+        int partNum = body.getPartsNum();
+        appendUintvarInteger(partNum);
+        for (int i = 0; i < partNum; i++) {
+            part = body.getPart(i);
+            mStack.newbuf();  // Leaving space for header lengh and data length
+            PositionMarker attachment = mStack.mark();
+
+            mStack.newbuf();  // Leaving space for Content-Type length
+            PositionMarker contentTypeBegin = mStack.mark();
+
+            byte[] partContentType = part.getContentType();
+
+            if (partContentType == null) {
+                // content type is mandatory
+                return PDU_COMPOSE_CONTENT_ERROR;
+            }
+
+            // content-type value
+            Integer partContentTypeIdentifier =
+                mContentTypeMap.get(new String(partContentType));
+            if (partContentTypeIdentifier == null) {
+                appendTextString(partContentType);
+            } else {
+                appendShortInteger(partContentTypeIdentifier.intValue());
+            }
+
+            /* Content-type parameter : name.
+             * The value of name, filename, content-location is the same.
+             * Just one of them is enough for this PDU.
+             */
+            byte[] name = part.getName();
+
+            if (null == name) {
+                name = part.getFilename();
+
+                if (null == name) {
+                    name = part.getContentLocation();
+
+                    if (null == name) {
+                        /* at lease one of name, filename, Content-location
+                         * should be available.
+                         */
+                        return PDU_COMPOSE_CONTENT_ERROR;
+                    }
+                }
+            }
+            appendOctet(PduPart.P_DEP_NAME);
+            appendTextString(name);
+
+            // content-type parameter : charset
+            int charset = part.getCharset();
+            if (charset != 0) {
+                appendOctet(PduPart.P_CHARSET);
+                appendShortInteger(charset);
+            }
+
+            int contentTypeLength = contentTypeBegin.getLength();
+            mStack.pop();
+            appendValueLength(contentTypeLength);
+            mStack.copy();
+
+            // content id
+            byte[] contentId = part.getContentId();
+
+            if (null != contentId) {
+                appendOctet(PduPart.P_CONTENT_ID);
+                if (('<' == contentId[0]) && ('>' == contentId[contentId.length - 1])) {
+                    appendQuotedString(contentId);
+                } else {
+                    appendQuotedString("<" + new String(contentId) + ">");
+                }
+            }
+
+            // content-location
+            byte[] contentLocation = part.getContentLocation();
+            if (null != contentLocation) {
+            	appendOctet(PduPart.P_CONTENT_LOCATION);
+            	appendTextString(contentLocation);
+            }
+
+            // content
+            int headerLength = attachment.getLength();
+
+            int dataLength = 0; // Just for safety...
+            byte[] partData = part.getData();
+
+            if (partData != null) {
+                arraycopy(partData, 0, partData.length);
+                dataLength = partData.length;
+            } else {
+                InputStream cr = null;
+                try {
+                    byte[] buffer = new byte[PDU_COMPOSER_BLOCK_SIZE];
+                    cr = mResolver.openInputStream(part.getDataUri());
+                    int len = 0;
+                    while ((len = cr.read(buffer)) != -1) {
+                        mMessage.write(buffer, 0, len);
+                        mPosition += len;
+                        dataLength += len;
+                    }
+                } catch (FileNotFoundException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (IOException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } catch (RuntimeException e) {
+                    return PDU_COMPOSE_CONTENT_ERROR;
+                } finally {
+                    if (cr != null) {
+                        try {
+                            cr.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+
+            if (dataLength != (attachment.getLength() - headerLength)) {
+                throw new RuntimeException("BUG: Length sanity check failed");
+            }
+
+            mStack.pop();
+            appendUintvarInteger(headerLength);
+            appendUintvarInteger(dataLength);
+            mStack.copy();
+        }
+
+        return PDU_COMPOSE_SUCCESS;
+    }
+
+    /**
+     *  Record current message informations.
+     */
+    static private class LengthRecordNode {
+        ByteArrayOutputStream currentMessage = null;
+        public int currentPosition = 0;
+
+        public LengthRecordNode next = null;
+    }
+
+    /**
+     * Mark current message position and stact size.
+     */
+    private class PositionMarker {
+        private int c_pos;   // Current position
+        private int currentStackSize;  // Current stack size
+
+        @UnsupportedAppUsage
+        int getLength() {
+            // If these assert fails, likely that you are finding the
+            // size of buffer that is deep in BufferStack you can only
+            // find the length of the buffer that is on top
+            if (currentStackSize != mStack.stackSize) {
+                throw new RuntimeException("BUG: Invalid call to getLength()");
+            }
+
+            return mPosition - c_pos;
+        }
+    }
+
+    /**
+     * This implementation can be OPTIMIZED to use only
+     * 2 buffers. This optimization involves changing BufferStack
+     * only... Its usage (interface) will not change.
+     */
+    private class BufferStack {
+        private LengthRecordNode stack = null;
+        private LengthRecordNode toCopy = null;
+
+        int stackSize = 0;
+
+        /**
+         *  Create a new message buffer and push it into the stack.
+         */
+        @UnsupportedAppUsage
+        void newbuf() {
+            // You can't create a new buff when toCopy != null
+            // That is after calling pop() and before calling copy()
+            // If you do, it is a bug
+            if (toCopy != null) {
+                throw new RuntimeException("BUG: Invalid newbuf() before copy()");
+            }
+
+            LengthRecordNode temp = new LengthRecordNode();
+
+            temp.currentMessage = mMessage;
+            temp.currentPosition = mPosition;
+
+            temp.next = stack;
+            stack = temp;
+
+            stackSize = stackSize + 1;
+
+            mMessage = new ByteArrayOutputStream();
+            mPosition = 0;
+        }
+
+        /**
+         *  Pop the message before and record current message in the stack.
+         */
+        @UnsupportedAppUsage
+        void pop() {
+            ByteArrayOutputStream currentMessage = mMessage;
+            int currentPosition = mPosition;
+
+            mMessage = stack.currentMessage;
+            mPosition = stack.currentPosition;
+
+            toCopy = stack;
+            // Re using the top element of the stack to avoid memory allocation
+
+            stack = stack.next;
+            stackSize = stackSize - 1;
+
+            toCopy.currentMessage = currentMessage;
+            toCopy.currentPosition = currentPosition;
+        }
+
+        /**
+         *  Append current message to the message before.
+         */
+        @UnsupportedAppUsage
+        void copy() {
+            arraycopy(toCopy.currentMessage.toByteArray(), 0,
+                    toCopy.currentPosition);
+
+            toCopy = null;
+        }
+
+        /**
+         *  Mark current message position
+         */
+        @UnsupportedAppUsage
+        PositionMarker mark() {
+            PositionMarker m = new PositionMarker();
+
+            m.c_pos = mPosition;
+            m.currentStackSize = stackSize;
+
+            return m;
+        }
+    }
+
+    /**
+     * Check address type.
+     *
+     * @param address address string without the postfix stinng type,
+     *        such as "/TYPE=PLMN", "/TYPE=IPv6" and "/TYPE=IPv4"
+     * @return PDU_PHONE_NUMBER_ADDRESS_TYPE if it is phone number,
+     *         PDU_EMAIL_ADDRESS_TYPE if it is email address,
+     *         PDU_IPV4_ADDRESS_TYPE if it is ipv4 address,
+     *         PDU_IPV6_ADDRESS_TYPE if it is ipv6 address,
+     *         PDU_UNKNOWN_ADDRESS_TYPE if it is unknown.
+     */
+    protected static int checkAddressType(String address) {
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf, section 8.
+         * address = ( e-mail / device-address / alphanum-shortcode / num-shortcode)
+         * e-mail = mailbox; to the definition of mailbox as described in
+         * section 3.4 of [RFC2822], but excluding the
+         * obsolete definitions as indicated by the "obs-" prefix.
+         * device-address = ( global-phone-number "/TYPE=PLMN" )
+         * / ( ipv4 "/TYPE=IPv4" ) / ( ipv6 "/TYPE=IPv6" )
+         * / ( escaped-value "/TYPE=" address-type )
+         *
+         * global-phone-number = ["+"] 1*( DIGIT / written-sep )
+         * written-sep =("-"/".")
+         *
+         * ipv4 = 1*3DIGIT 3( "." 1*3DIGIT ) ; IPv4 address value
+         *
+         * ipv6 = 4HEXDIG 7( ":" 4HEXDIG ) ; IPv6 address per RFC 2373
+         */
+
+        if (null == address) {
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+
+        if (address.matches(REGEXP_IPV4_ADDRESS_TYPE)) {
+            // Ipv4 address.
+            return PDU_IPV4_ADDRESS_TYPE;
+        }else if (address.matches(REGEXP_PHONE_NUMBER_ADDRESS_TYPE)) {
+            // Phone number.
+            return PDU_PHONE_NUMBER_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_EMAIL_ADDRESS_TYPE)) {
+            // Email address.
+            return PDU_EMAIL_ADDRESS_TYPE;
+        } else if (address.matches(REGEXP_IPV6_ADDRESS_TYPE)) {
+            // Ipv6 address.
+            return PDU_IPV6_ADDRESS_TYPE;
+        } else {
+            // Unknown address.
+            return PDU_UNKNOWN_ADDRESS_TYPE;
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduContentTypes.java b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
new file mode 100644
index 0000000..8551b2f
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduContentTypes.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class PduContentTypes {
+    /**
+     * All content types. From:
+     * http://www.openmobilealliance.org/tech/omna/omna-wsp-content-type.htm
+     */
+    @UnsupportedAppUsage
+    static final String[] contentTypes = {
+        "*/*",                                        /* 0x00 */
+        "text/*",                                     /* 0x01 */
+        "text/html",                                  /* 0x02 */
+        "text/plain",                                 /* 0x03 */
+        "text/x-hdml",                                /* 0x04 */
+        "text/x-ttml",                                /* 0x05 */
+        "text/x-vCalendar",                           /* 0x06 */
+        "text/x-vCard",                               /* 0x07 */
+        "text/vnd.wap.wml",                           /* 0x08 */
+        "text/vnd.wap.wmlscript",                     /* 0x09 */
+        "text/vnd.wap.wta-event",                     /* 0x0A */
+        "multipart/*",                                /* 0x0B */
+        "multipart/mixed",                            /* 0x0C */
+        "multipart/form-data",                        /* 0x0D */
+        "multipart/byterantes",                       /* 0x0E */
+        "multipart/alternative",                      /* 0x0F */
+        "application/*",                              /* 0x10 */
+        "application/java-vm",                        /* 0x11 */
+        "application/x-www-form-urlencoded",          /* 0x12 */
+        "application/x-hdmlc",                        /* 0x13 */
+        "application/vnd.wap.wmlc",                   /* 0x14 */
+        "application/vnd.wap.wmlscriptc",             /* 0x15 */
+        "application/vnd.wap.wta-eventc",             /* 0x16 */
+        "application/vnd.wap.uaprof",                 /* 0x17 */
+        "application/vnd.wap.wtls-ca-certificate",    /* 0x18 */
+        "application/vnd.wap.wtls-user-certificate",  /* 0x19 */
+        "application/x-x509-ca-cert",                 /* 0x1A */
+        "application/x-x509-user-cert",               /* 0x1B */
+        "image/*",                                    /* 0x1C */
+        "image/gif",                                  /* 0x1D */
+        "image/jpeg",                                 /* 0x1E */
+        "image/tiff",                                 /* 0x1F */
+        "image/png",                                  /* 0x20 */
+        "image/vnd.wap.wbmp",                         /* 0x21 */
+        "application/vnd.wap.multipart.*",            /* 0x22 */
+        "application/vnd.wap.multipart.mixed",        /* 0x23 */
+        "application/vnd.wap.multipart.form-data",    /* 0x24 */
+        "application/vnd.wap.multipart.byteranges",   /* 0x25 */
+        "application/vnd.wap.multipart.alternative",  /* 0x26 */
+        "application/xml",                            /* 0x27 */
+        "text/xml",                                   /* 0x28 */
+        "application/vnd.wap.wbxml",                  /* 0x29 */
+        "application/x-x968-cross-cert",              /* 0x2A */
+        "application/x-x968-ca-cert",                 /* 0x2B */
+        "application/x-x968-user-cert",               /* 0x2C */
+        "text/vnd.wap.si",                            /* 0x2D */
+        "application/vnd.wap.sic",                    /* 0x2E */
+        "text/vnd.wap.sl",                            /* 0x2F */
+        "application/vnd.wap.slc",                    /* 0x30 */
+        "text/vnd.wap.co",                            /* 0x31 */
+        "application/vnd.wap.coc",                    /* 0x32 */
+        "application/vnd.wap.multipart.related",      /* 0x33 */
+        "application/vnd.wap.sia",                    /* 0x34 */
+        "text/vnd.wap.connectivity-xml",              /* 0x35 */
+        "application/vnd.wap.connectivity-wbxml",     /* 0x36 */
+        "application/pkcs7-mime",                     /* 0x37 */
+        "application/vnd.wap.hashed-certificate",     /* 0x38 */
+        "application/vnd.wap.signed-certificate",     /* 0x39 */
+        "application/vnd.wap.cert-response",          /* 0x3A */
+        "application/xhtml+xml",                      /* 0x3B */
+        "application/wml+xml",                        /* 0x3C */
+        "text/css",                                   /* 0x3D */
+        "application/vnd.wap.mms-message",            /* 0x3E */
+        "application/vnd.wap.rollover-certificate",   /* 0x3F */
+        "application/vnd.wap.locc+wbxml",             /* 0x40 */
+        "application/vnd.wap.loc+xml",                /* 0x41 */
+        "application/vnd.syncml.dm+wbxml",            /* 0x42 */
+        "application/vnd.syncml.dm+xml",              /* 0x43 */
+        "application/vnd.syncml.notification",        /* 0x44 */
+        "application/vnd.wap.xhtml+xml",              /* 0x45 */
+        "application/vnd.wv.csp.cir",                 /* 0x46 */
+        "application/vnd.oma.dd+xml",                 /* 0x47 */
+        "application/vnd.oma.drm.message",            /* 0x48 */
+        "application/vnd.oma.drm.content",            /* 0x49 */
+        "application/vnd.oma.drm.rights+xml",         /* 0x4A */
+        "application/vnd.oma.drm.rights+wbxml",       /* 0x4B */
+        "application/vnd.wv.csp+xml",                 /* 0x4C */
+        "application/vnd.wv.csp+wbxml",               /* 0x4D */
+        "application/vnd.syncml.ds.notification",     /* 0x4E */
+        "audio/*",                                    /* 0x4F */
+        "video/*",                                    /* 0x50 */
+        "application/vnd.oma.dd2+xml",                /* 0x51 */
+        "application/mikey"                           /* 0x52 */
+    };
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduHeaders.java b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
new file mode 100644
index 0000000..b524464
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduHeaders.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PduHeaders {
+    /**
+     * All pdu header fields.
+     */
+    public static final int BCC                             = 0x81;
+    public static final int CC                              = 0x82;
+    public static final int CONTENT_LOCATION                = 0x83;
+    public static final int CONTENT_TYPE                    = 0x84;
+    public static final int DATE                            = 0x85;
+    public static final int DELIVERY_REPORT                 = 0x86;
+    public static final int DELIVERY_TIME                   = 0x87;
+    public static final int EXPIRY                          = 0x88;
+    public static final int FROM                            = 0x89;
+    public static final int MESSAGE_CLASS                   = 0x8A;
+    public static final int MESSAGE_ID                      = 0x8B;
+    public static final int MESSAGE_TYPE                    = 0x8C;
+    public static final int MMS_VERSION                     = 0x8D;
+    public static final int MESSAGE_SIZE                    = 0x8E;
+    public static final int PRIORITY                        = 0x8F;
+
+    public static final int READ_REPLY                      = 0x90;
+    public static final int READ_REPORT                     = 0x90;
+    public static final int REPORT_ALLOWED                  = 0x91;
+    public static final int RESPONSE_STATUS                 = 0x92;
+    public static final int RESPONSE_TEXT                   = 0x93;
+    public static final int SENDER_VISIBILITY               = 0x94;
+    public static final int STATUS                          = 0x95;
+    public static final int SUBJECT                         = 0x96;
+    public static final int TO                              = 0x97;
+    public static final int TRANSACTION_ID                  = 0x98;
+    public static final int RETRIEVE_STATUS                 = 0x99;
+    public static final int RETRIEVE_TEXT                   = 0x9A;
+    public static final int READ_STATUS                     = 0x9B;
+    public static final int REPLY_CHARGING                  = 0x9C;
+    public static final int REPLY_CHARGING_DEADLINE         = 0x9D;
+    public static final int REPLY_CHARGING_ID               = 0x9E;
+    public static final int REPLY_CHARGING_SIZE             = 0x9F;
+
+    public static final int PREVIOUSLY_SENT_BY              = 0xA0;
+    public static final int PREVIOUSLY_SENT_DATE            = 0xA1;
+    public static final int STORE                           = 0xA2;
+    public static final int MM_STATE                        = 0xA3;
+    public static final int MM_FLAGS                        = 0xA4;
+    public static final int STORE_STATUS                    = 0xA5;
+    public static final int STORE_STATUS_TEXT               = 0xA6;
+    public static final int STORED                          = 0xA7;
+    public static final int ATTRIBUTES                      = 0xA8;
+    public static final int TOTALS                          = 0xA9;
+    public static final int MBOX_TOTALS                     = 0xAA;
+    public static final int QUOTAS                          = 0xAB;
+    public static final int MBOX_QUOTAS                     = 0xAC;
+    public static final int MESSAGE_COUNT                   = 0xAD;
+    public static final int CONTENT                         = 0xAE;
+    public static final int START                           = 0xAF;
+
+    public static final int ADDITIONAL_HEADERS              = 0xB0;
+    public static final int DISTRIBUTION_INDICATOR          = 0xB1;
+    public static final int ELEMENT_DESCRIPTOR              = 0xB2;
+    public static final int LIMIT                           = 0xB3;
+    public static final int RECOMMENDED_RETRIEVAL_MODE      = 0xB4;
+    public static final int RECOMMENDED_RETRIEVAL_MODE_TEXT = 0xB5;
+    public static final int STATUS_TEXT                     = 0xB6;
+    public static final int APPLIC_ID                       = 0xB7;
+    public static final int REPLY_APPLIC_ID                 = 0xB8;
+    public static final int AUX_APPLIC_ID                   = 0xB9;
+    public static final int CONTENT_CLASS                   = 0xBA;
+    public static final int DRM_CONTENT                     = 0xBB;
+    public static final int ADAPTATION_ALLOWED              = 0xBC;
+    public static final int REPLACE_ID                      = 0xBD;
+    public static final int CANCEL_ID                       = 0xBE;
+    public static final int CANCEL_STATUS                   = 0xBF;
+
+    /**
+     * X-Mms-Message-Type field types.
+     */
+    public static final int MESSAGE_TYPE_SEND_REQ           = 0x80;
+    public static final int MESSAGE_TYPE_SEND_CONF          = 0x81;
+    public static final int MESSAGE_TYPE_NOTIFICATION_IND   = 0x82;
+    public static final int MESSAGE_TYPE_NOTIFYRESP_IND     = 0x83;
+    public static final int MESSAGE_TYPE_RETRIEVE_CONF      = 0x84;
+    public static final int MESSAGE_TYPE_ACKNOWLEDGE_IND    = 0x85;
+    public static final int MESSAGE_TYPE_DELIVERY_IND       = 0x86;
+    public static final int MESSAGE_TYPE_READ_REC_IND       = 0x87;
+    public static final int MESSAGE_TYPE_READ_ORIG_IND      = 0x88;
+    public static final int MESSAGE_TYPE_FORWARD_REQ        = 0x89;
+    public static final int MESSAGE_TYPE_FORWARD_CONF       = 0x8A;
+    public static final int MESSAGE_TYPE_MBOX_STORE_REQ     = 0x8B;
+    public static final int MESSAGE_TYPE_MBOX_STORE_CONF    = 0x8C;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_REQ      = 0x8D;
+    public static final int MESSAGE_TYPE_MBOX_VIEW_CONF     = 0x8E;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_REQ    = 0x8F;
+    public static final int MESSAGE_TYPE_MBOX_UPLOAD_CONF   = 0x90;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_REQ    = 0x91;
+    public static final int MESSAGE_TYPE_MBOX_DELETE_CONF   = 0x92;
+    public static final int MESSAGE_TYPE_MBOX_DESCR         = 0x93;
+    public static final int MESSAGE_TYPE_DELETE_REQ         = 0x94;
+    public static final int MESSAGE_TYPE_DELETE_CONF        = 0x95;
+    public static final int MESSAGE_TYPE_CANCEL_REQ         = 0x96;
+    public static final int MESSAGE_TYPE_CANCEL_CONF        = 0x97;
+
+    /**
+     *  X-Mms-Delivery-Report |
+     *  X-Mms-Read-Report |
+     *  X-Mms-Report-Allowed |
+     *  X-Mms-Sender-Visibility |
+     *  X-Mms-Store |
+     *  X-Mms-Stored |
+     *  X-Mms-Totals |
+     *  X-Mms-Quotas |
+     *  X-Mms-Distribution-Indicator |
+     *  X-Mms-DRM-Content |
+     *  X-Mms-Adaptation-Allowed |
+     *  field types.
+     */
+    public static final int VALUE_YES                       = 0x80;
+    public static final int VALUE_NO                        = 0x81;
+
+    /**
+     *  Delivery-Time |
+     *  Expiry and Reply-Charging-Deadline |
+     *  field type components.
+     */
+    public static final int VALUE_ABSOLUTE_TOKEN            = 0x80;
+    public static final int VALUE_RELATIVE_TOKEN            = 0x81;
+
+    /**
+     * X-Mms-MMS-Version field types.
+     */
+    public static final int MMS_VERSION_1_3                 = ((1 << 4) | 3);
+    public static final int MMS_VERSION_1_2                 = ((1 << 4) | 2);
+    public static final int MMS_VERSION_1_1                 = ((1 << 4) | 1);
+    public static final int MMS_VERSION_1_0                 = ((1 << 4) | 0);
+
+    // Current version is 1.2.
+    public static final int CURRENT_MMS_VERSION             = MMS_VERSION_1_2;
+
+    /**
+     *  From field type components.
+     */
+    public static final int FROM_ADDRESS_PRESENT_TOKEN      = 0x80;
+    public static final int FROM_INSERT_ADDRESS_TOKEN       = 0x81;
+
+    public static final String FROM_ADDRESS_PRESENT_TOKEN_STR = "address-present-token";
+    public static final String FROM_INSERT_ADDRESS_TOKEN_STR = "insert-address-token";
+
+    /**
+     *  X-Mms-Status Field.
+     */
+    public static final int STATUS_EXPIRED                  = 0x80;
+    public static final int STATUS_RETRIEVED                = 0x81;
+    public static final int STATUS_REJECTED                 = 0x82;
+    public static final int STATUS_DEFERRED                 = 0x83;
+    public static final int STATUS_UNRECOGNIZED             = 0x84;
+    public static final int STATUS_INDETERMINATE            = 0x85;
+    public static final int STATUS_FORWARDED                = 0x86;
+    public static final int STATUS_UNREACHABLE              = 0x87;
+
+    /**
+     *  MM-Flags field type components.
+     */
+    public static final int MM_FLAGS_ADD_TOKEN              = 0x80;
+    public static final int MM_FLAGS_REMOVE_TOKEN           = 0x81;
+    public static final int MM_FLAGS_FILTER_TOKEN           = 0x82;
+
+    /**
+     *  X-Mms-Message-Class field types.
+     */
+    public static final int MESSAGE_CLASS_PERSONAL          = 0x80;
+    public static final int MESSAGE_CLASS_ADVERTISEMENT     = 0x81;
+    public static final int MESSAGE_CLASS_INFORMATIONAL     = 0x82;
+    public static final int MESSAGE_CLASS_AUTO              = 0x83;
+
+    public static final String MESSAGE_CLASS_PERSONAL_STR = "personal";
+    public static final String MESSAGE_CLASS_ADVERTISEMENT_STR = "advertisement";
+    public static final String MESSAGE_CLASS_INFORMATIONAL_STR = "informational";
+    public static final String MESSAGE_CLASS_AUTO_STR = "auto";
+
+    /**
+     *  X-Mms-Priority field types.
+     */
+    public static final int PRIORITY_LOW                    = 0x80;
+    public static final int PRIORITY_NORMAL                 = 0x81;
+    public static final int PRIORITY_HIGH                   = 0x82;
+
+    /**
+     *  X-Mms-Response-Status field types.
+     */
+    public static final int RESPONSE_STATUS_OK                   = 0x80;
+    public static final int RESPONSE_STATUS_ERROR_UNSPECIFIED    = 0x81;
+    public static final int RESPONSE_STATUS_ERROR_SERVICE_DENIED = 0x82;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_FORMAT_CORRUPT     = 0x83;
+    public static final int RESPONSE_STATUS_ERROR_SENDING_ADDRESS_UNRESOLVED = 0x84;
+
+    public static final int RESPONSE_STATUS_ERROR_MESSAGE_NOT_FOUND    = 0x85;
+    public static final int RESPONSE_STATUS_ERROR_NETWORK_PROBLEM      = 0x86;
+    public static final int RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED = 0x87;
+    public static final int RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE  = 0x88;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE    = 0xC0;
+
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_SENDNG_ADDRESS_UNRESOLVED = 0xC1;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND         = 0xC2;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM           = 0xC3;
+    public static final int RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS           = 0xC4;
+
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_FAILURE                             = 0xE0;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SERVICE_DENIED                      = 0xE1;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT              = 0xE2;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_SENDING_ADDRESS_UNRESOLVED          = 0xE3;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND                   = 0xE4;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED                = 0xE5;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_LIMITATIONS_NOT_MET  = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_REQUEST_NOT_ACCEPTED = 0xE6;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_FORWARDING_DENIED    = 0xE8;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_REPLY_CHARGING_NOT_SUPPORTED        = 0xE9;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_ADDRESS_HIDING_NOT_SUPPORTED        = 0xEA;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID                     = 0xEB;
+    public static final int RESPONSE_STATUS_ERROR_PERMANENT_END                                 = 0xFF;
+
+    /**
+     *  X-Mms-Retrieve-Status field types.
+     */
+    public static final int RETRIEVE_STATUS_OK                                  = 0x80;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE             = 0xC0;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 0xC1;
+    public static final int RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM     = 0xC2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE             = 0xE0;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_SERVICE_DENIED      = 0xE1;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 0xE2;
+    public static final int RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 0xE3;
+    public static final int RETRIEVE_STATUS_ERROR_END                           = 0xFF;
+
+    /**
+     *  X-Mms-Sender-Visibility field types.
+     */
+    public static final int SENDER_VISIBILITY_HIDE          = 0x80;
+    public static final int SENDER_VISIBILITY_SHOW          = 0x81;
+
+    /**
+     *  X-Mms-Read-Status field types.
+     */
+    public static final int READ_STATUS_READ                        = 0x80;
+    public static final int READ_STATUS__DELETED_WITHOUT_BEING_READ = 0x81;
+
+    /**
+     *  X-Mms-Cancel-Status field types.
+     */
+    public static final int CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED = 0x80;
+    public static final int CANCEL_STATUS_REQUEST_CORRUPTED             = 0x81;
+
+    /**
+     *  X-Mms-Reply-Charging field types.
+     */
+    public static final int REPLY_CHARGING_REQUESTED           = 0x80;
+    public static final int REPLY_CHARGING_REQUESTED_TEXT_ONLY = 0x81;
+    public static final int REPLY_CHARGING_ACCEPTED            = 0x82;
+    public static final int REPLY_CHARGING_ACCEPTED_TEXT_ONLY  = 0x83;
+
+    /**
+     *  X-Mms-MM-State field types.
+     */
+    public static final int MM_STATE_DRAFT                  = 0x80;
+    public static final int MM_STATE_SENT                   = 0x81;
+    public static final int MM_STATE_NEW                    = 0x82;
+    public static final int MM_STATE_RETRIEVED              = 0x83;
+    public static final int MM_STATE_FORWARDED              = 0x84;
+
+    /**
+     * X-Mms-Recommended-Retrieval-Mode field types.
+     */
+    public static final int RECOMMENDED_RETRIEVAL_MODE_MANUAL = 0x80;
+
+    /**
+     *  X-Mms-Content-Class field types.
+     */
+    public static final int CONTENT_CLASS_TEXT              = 0x80;
+    public static final int CONTENT_CLASS_IMAGE_BASIC       = 0x81;
+    public static final int CONTENT_CLASS_IMAGE_RICH        = 0x82;
+    public static final int CONTENT_CLASS_VIDEO_BASIC       = 0x83;
+    public static final int CONTENT_CLASS_VIDEO_RICH        = 0x84;
+    public static final int CONTENT_CLASS_MEGAPIXEL         = 0x85;
+    public static final int CONTENT_CLASS_CONTENT_BASIC     = 0x86;
+    public static final int CONTENT_CLASS_CONTENT_RICH      = 0x87;
+
+    /**
+     *  X-Mms-Store-Status field types.
+     */
+    public static final int STORE_STATUS_SUCCESS                                = 0x80;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_FAILURE                = 0xC0;
+    public static final int STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM        = 0xC1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_FAILURE                = 0xE0;
+    public static final int STORE_STATUS_ERROR_PERMANENT_SERVICE_DENIED         = 0xE1;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_FORMAT_CORRUPT = 0xE2;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND      = 0xE3;
+    public static final int STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL             = 0xE4;
+    public static final int STORE_STATUS_ERROR_END                              = 0xFF;
+
+    /**
+     * The map contains the value of all headers.
+     */
+    private HashMap<Integer, Object> mHeaderMap = null;
+
+    /**
+     * Constructor of PduHeaders.
+     */
+    @UnsupportedAppUsage
+    public PduHeaders() {
+        mHeaderMap = new HashMap<Integer, Object>();
+    }
+
+    /**
+     * Get octet value by header field.
+     *
+     * @param field the field
+     * @return the octet value of the pdu header
+     *          with specified header field. Return 0 if
+     *          the value is not set.
+     */
+    @UnsupportedAppUsage
+    protected int getOctet(int field) {
+        Integer octet = (Integer) mHeaderMap.get(field);
+        if (null == octet) {
+            return 0;
+        }
+
+        return octet;
+    }
+
+    /**
+     * Set octet value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    protected void setOctet(int value, int field)
+            throws InvalidHeaderValueException{
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case REPORT_ALLOWED:
+            case ADAPTATION_ALLOWED:
+            case DELIVERY_REPORT:
+            case DRM_CONTENT:
+            case DISTRIBUTION_INDICATOR:
+            case QUOTAS:
+            case READ_REPORT:
+            case STORE:
+            case STORED:
+            case TOTALS:
+            case SENDER_VISIBILITY:
+                if ((VALUE_YES != value) && (VALUE_NO != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case READ_STATUS:
+                if ((READ_STATUS_READ != value) &&
+                        (READ_STATUS__DELETED_WITHOUT_BEING_READ != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CANCEL_STATUS:
+                if ((CANCEL_STATUS_REQUEST_SUCCESSFULLY_RECEIVED != value) &&
+                        (CANCEL_STATUS_REQUEST_CORRUPTED != value)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case PRIORITY:
+                if ((value < PRIORITY_LOW) || (value > PRIORITY_HIGH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case STATUS:
+                if ((value < STATUS_EXPIRED) || (value > STATUS_UNREACHABLE)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case REPLY_CHARGING:
+                if ((value < REPLY_CHARGING_REQUESTED)
+                        || (value > REPLY_CHARGING_ACCEPTED_TEXT_ONLY)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case MM_STATE:
+                if ((value < MM_STATE_DRAFT) || (value > MM_STATE_FORWARDED)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RECOMMENDED_RETRIEVAL_MODE:
+                if (RECOMMENDED_RETRIEVAL_MODE_MANUAL != value) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case CONTENT_CLASS:
+                if ((value < CONTENT_CLASS_TEXT)
+                        || (value > CONTENT_CLASS_CONTENT_RICH)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            case RETRIEVE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.50, we modify the invalid value.
+                if ((value > RETRIEVE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > RETRIEVE_STATUS_ERROR_PERMANENT_CONTENT_UNSUPPORTED) &&
+                        (value <= RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < RETRIEVE_STATUS_OK) ||
+                        ((value > RETRIEVE_STATUS_OK) &&
+                                (value < RETRIEVE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RETRIEVE_STATUS_ERROR_END)) {
+                    value = RETRIEVE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case STORE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.58, we modify the invalid value.
+                if ((value > STORE_STATUS_ERROR_TRANSIENT_NETWORK_PROBLEM) &&
+                        (value < STORE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = STORE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if ((value > STORE_STATUS_ERROR_PERMANENT_MMBOX_FULL) &&
+                        (value <= STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                } else if ((value < STORE_STATUS_SUCCESS) ||
+                        ((value > STORE_STATUS_SUCCESS) &&
+                                (value < STORE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > STORE_STATUS_ERROR_END)) {
+                    value = STORE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case RESPONSE_STATUS:
+                // According to oma-ts-mms-enc-v1_3, section 7.3.48, we modify the invalid value.
+                if ((value > RESPONSE_STATUS_ERROR_TRANSIENT_PARTIAL_SUCCESS) &&
+                        (value < RESPONSE_STATUS_ERROR_PERMANENT_FAILURE)) {
+                    value = RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE;
+                } else if (((value > RESPONSE_STATUS_ERROR_PERMANENT_LACK_OF_PREPAID) &&
+                        (value <= RESPONSE_STATUS_ERROR_PERMANENT_END)) ||
+                        (value < RESPONSE_STATUS_OK) ||
+                        ((value > RESPONSE_STATUS_ERROR_UNSUPPORTED_MESSAGE) &&
+                                (value < RESPONSE_STATUS_ERROR_TRANSIENT_FAILURE)) ||
+                                (value > RESPONSE_STATUS_ERROR_PERMANENT_END)) {
+                    value = RESPONSE_STATUS_ERROR_PERMANENT_FAILURE;
+                }
+                break;
+            case MMS_VERSION:
+                if ((value < MMS_VERSION_1_0)|| (value > MMS_VERSION_1_3)) {
+                    value = CURRENT_MMS_VERSION; // Current version is the default value.
+                }
+                break;
+            case MESSAGE_TYPE:
+                if ((value < MESSAGE_TYPE_SEND_REQ) || (value > MESSAGE_TYPE_CANCEL_CONF)) {
+                    // Invalid value.
+                    throw new InvalidHeaderValueException("Invalid Octet value!");
+                }
+                break;
+            default:
+                // This header value should not be Octect.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get TextString value by header field.
+     *
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected byte[] getTextString(int field) {
+        return (byte[]) mHeaderMap.get(field);
+    }
+
+    /**
+     * Set TextString value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the TextString value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setTextString(byte[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case TRANSACTION_ID:
+            case REPLY_CHARGING_ID:
+            case AUX_APPLIC_ID:
+            case APPLIC_ID:
+            case REPLY_APPLIC_ID:
+            case MESSAGE_ID:
+            case REPLACE_ID:
+            case CANCEL_ID:
+            case CONTENT_LOCATION:
+            case MESSAGE_CLASS:
+            case CONTENT_TYPE:
+                break;
+            default:
+                // This header value should not be Text-String.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Get EncodedStringValue value by header field.
+     *
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected EncodedStringValue getEncodedStringValue(int field) {
+        return (EncodedStringValue) mHeaderMap.get(field);
+    }
+
+    /**
+     * Get TO, CC or BCC header value.
+     *
+     * @param field the field
+     * @return the EncodeStringValue array of the pdu header
+     *          with specified header field
+     */
+    @UnsupportedAppUsage
+    protected EncodedStringValue[] getEncodedStringValues(int field) {
+        ArrayList<EncodedStringValue> list =
+                (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            return null;
+        }
+        EncodedStringValue[] values = new EncodedStringValue[list.size()];
+        return list.toArray(values);
+    }
+
+    /**
+     * Set EncodedStringValue value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    protected void setEncodedStringValue(EncodedStringValue value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case SUBJECT:
+            case RECOMMENDED_RETRIEVAL_MODE_TEXT:
+            case RETRIEVE_TEXT:
+            case STATUS_TEXT:
+            case STORE_STATUS_TEXT:
+            case RESPONSE_TEXT:
+            case FROM:
+            case PREVIOUSLY_SENT_BY:
+            case MM_FLAGS:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        mHeaderMap.put(field, value);
+    }
+
+    /**
+     * Set TO, CC or BCC header value.
+     *
+     * @param value the value
+     * @param field the field
+     * @return the EncodedStringValue value array of the pdu header
+     *          with specified header field
+     * @throws NullPointerException if the value is null.
+     */
+    protected void setEncodedStringValues(EncodedStringValue[] value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                // This header value should not be Encoded-String-Value.
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list = new ArrayList<EncodedStringValue>();
+        for (int i = 0; i < value.length; i++) {
+            list.add(value[i]);
+        }
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Append one EncodedStringValue to another.
+     *
+     * @param value the EncodedStringValue to append
+     * @param field the field
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    protected void appendEncodedStringValue(EncodedStringValue value,
+                                    int field) {
+        if (null == value) {
+            throw new NullPointerException();
+        }
+
+        switch (field) {
+            case BCC:
+            case CC:
+            case TO:
+                break;
+            default:
+                throw new RuntimeException("Invalid header field!");
+        }
+
+        ArrayList<EncodedStringValue> list =
+            (ArrayList<EncodedStringValue>) mHeaderMap.get(field);
+        if (null == list) {
+            list  = new ArrayList<EncodedStringValue>();
+        }
+        list.add(value);
+        mHeaderMap.put(field, list);
+    }
+
+    /**
+     * Get LongInteger value by header field.
+     *
+     * @param field the field
+     * @return the LongInteger value of the pdu header
+     *          with specified header field. if return -1, the
+     *          field is not existed in pdu header.
+     */
+    @UnsupportedAppUsage
+    protected long getLongInteger(int field) {
+        Long longInteger = (Long) mHeaderMap.get(field);
+        if (null == longInteger) {
+            return -1;
+        }
+
+        return longInteger.longValue();
+    }
+
+    /**
+     * Set LongInteger value to pdu header by header field.
+     *
+     * @param value the value
+     * @param field the field
+     */
+    @UnsupportedAppUsage
+    protected void setLongInteger(long value, int field) {
+        /**
+         * Check whether this field can be set for specific
+         * header and check validity of the field.
+         */
+        switch (field) {
+            case DATE:
+            case REPLY_CHARGING_SIZE:
+            case MESSAGE_SIZE:
+            case MESSAGE_COUNT:
+            case START:
+            case LIMIT:
+            case DELIVERY_TIME:
+            case EXPIRY:
+            case REPLY_CHARGING_DEADLINE:
+            case PREVIOUSLY_SENT_DATE:
+                break;
+            default:
+                // This header value should not be LongInteger.
+                throw new RuntimeException("Invalid header field!");
+        }
+        mHeaderMap.put(field, value);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduParser.java b/telephony/java/com/google/android/mms/pdu/PduParser.java
new file mode 100755
index 0000000..f483994
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduParser.java
@@ -0,0 +1,2023 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class PduParser {
+    /**
+     *  The next are WAP values defined in WSP specification.
+     */
+    private static final int QUOTE = 127;
+    private static final int LENGTH_QUOTE = 31;
+    private static final int TEXT_MIN = 32;
+    private static final int TEXT_MAX = 127;
+    private static final int SHORT_INTEGER_MAX = 127;
+    private static final int SHORT_LENGTH_MAX = 30;
+    private static final int LONG_INTEGER_LENGTH_MAX = 8;
+    private static final int QUOTED_STRING_FLAG = 34;
+    private static final int END_STRING_FLAG = 0x00;
+    //The next two are used by the interface "parseWapString" to
+    //distinguish Text-String and Quoted-String.
+    private static final int TYPE_TEXT_STRING = 0;
+    private static final int TYPE_QUOTED_STRING = 1;
+    private static final int TYPE_TOKEN_STRING = 2;
+
+    /**
+     * Specify the part position.
+     */
+    private static final int THE_FIRST_PART = 0;
+    private static final int THE_LAST_PART = 1;
+
+    /**
+     * The pdu data.
+     */
+    private ByteArrayInputStream mPduDataStream = null;
+
+    /**
+     * Store pdu headers
+     */
+    private PduHeaders mHeaders = null;
+
+    /**
+     * Store pdu parts.
+     */
+    private PduBody mBody = null;
+
+    /**
+     * Store the "type" parameter in "Content-Type" header field.
+     */
+    private static byte[] mTypeParam = null;
+
+    /**
+     * Store the "start" parameter in "Content-Type" header field.
+     */
+    private static byte[] mStartParam = null;
+
+    /**
+     * The log tag.
+     */
+    private static final String LOG_TAG = "PduParser";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    /**
+     * Whether to parse content-disposition part header
+     */
+    private final boolean mParseContentDisposition;
+
+    /**
+     * Constructor.
+     *
+     * @param pduDataStream pdu data to be parsed
+     * @param parseContentDisposition whether to parse the Content-Disposition part header
+     */
+    @UnsupportedAppUsage
+    public PduParser(byte[] pduDataStream, boolean parseContentDisposition) {
+        mPduDataStream = new ByteArrayInputStream(pduDataStream);
+        mParseContentDisposition = parseContentDisposition;
+    }
+
+    /**
+     * Parse the pdu.
+     *
+     * @return the pdu structure if parsing successfully.
+     *         null if parsing error happened or mandatory fields are not set.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu parse(){
+        if (mPduDataStream == null) {
+            return null;
+        }
+
+        /* parse headers */
+        mHeaders = parseHeaders(mPduDataStream);
+        if (null == mHeaders) {
+            // Parse headers failed.
+            return null;
+        }
+
+        /* get the message type */
+        int messageType = mHeaders.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check mandatory header fields */
+        if (false == checkMandatoryHeader(mHeaders)) {
+            log("check mandatory headers failed!");
+            return null;
+        }
+
+        if ((PduHeaders.MESSAGE_TYPE_SEND_REQ == messageType) ||
+                (PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF == messageType)) {
+            /* need to parse the parts */
+            mBody = parseParts(mPduDataStream);
+            if (null == mBody) {
+                // Parse parts failed.
+                return null;
+            }
+        }
+
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_REQ");
+                }
+                SendReq sendReq = new SendReq(mHeaders, mBody);
+                return sendReq;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_SEND_CONF");
+                }
+                SendConf sendConf = new SendConf(mHeaders);
+                return sendConf;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFICATION_IND");
+                }
+                NotificationInd notificationInd =
+                    new NotificationInd(mHeaders);
+                return notificationInd;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_NOTIFYRESP_IND");
+                }
+                NotifyRespInd notifyRespInd =
+                    new NotifyRespInd(mHeaders);
+                return notifyRespInd;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_RETRIEVE_CONF");
+                }
+                RetrieveConf retrieveConf =
+                    new RetrieveConf(mHeaders, mBody);
+
+                byte[] contentType = retrieveConf.getContentType();
+                if (null == contentType) {
+                    return null;
+                }
+                String ctTypeStr = new String(contentType);
+                if (ctTypeStr.equals(ContentType.MULTIPART_MIXED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_RELATED)
+                        || ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // The MMS content type must be "application/vnd.wap.multipart.mixed"
+                    // or "application/vnd.wap.multipart.related"
+                    // or "application/vnd.wap.multipart.alternative"
+                    return retrieveConf;
+                } else if (ctTypeStr.equals(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // "application/vnd.wap.multipart.alternative"
+                    // should take only the first part.
+                    PduPart firstPart = mBody.getPart(0);
+                    mBody.removeAll();
+                    mBody.addPart(0, firstPart);
+                    return retrieveConf;
+                }
+                return null;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_DELIVERY_IND");
+                }
+                DeliveryInd deliveryInd =
+                    new DeliveryInd(mHeaders);
+                return deliveryInd;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_ACKNOWLEDGE_IND");
+                }
+                AcknowledgeInd acknowledgeInd =
+                    new AcknowledgeInd(mHeaders);
+                return acknowledgeInd;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_ORIG_IND");
+                }
+                ReadOrigInd readOrigInd =
+                    new ReadOrigInd(mHeaders);
+                return readOrigInd;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "parse: MESSAGE_TYPE_READ_REC_IND");
+                }
+                ReadRecInd readRecInd =
+                    new ReadRecInd(mHeaders);
+                return readRecInd;
+            default:
+                log("Parser doesn't support this message type in this version!");
+            return null;
+        }
+    }
+
+    /**
+     * Parse pdu headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return headers in PduHeaders structure, null when parse fail
+     */
+    protected PduHeaders parseHeaders(ByteArrayInputStream pduDataStream){
+        if (pduDataStream == null) {
+            return null;
+        }
+        boolean keepParsing = true;
+        PduHeaders headers = new PduHeaders();
+
+        while (keepParsing && (pduDataStream.available() > 0)) {
+            pduDataStream.mark(1);
+            int headerField = extractByteValue(pduDataStream);
+            /* parse custom text header */
+            if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+                pduDataStream.reset();
+                byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+                }
+                /* we should ignore it at the moment */
+                continue;
+            }
+            switch (headerField) {
+                case PduHeaders.MESSAGE_TYPE:
+                {
+                    int messageType = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: messageType: " + messageType);
+                    }
+                    switch (messageType) {
+                        // We don't support these kind of messages now.
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+                        case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+                        case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                            return null;
+                    }
+                    try {
+                        headers.setOctet(messageType, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + messageType +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+                /* Octect value */
+                case PduHeaders.REPORT_ALLOWED:
+                case PduHeaders.ADAPTATION_ALLOWED:
+                case PduHeaders.DELIVERY_REPORT:
+                case PduHeaders.DRM_CONTENT:
+                case PduHeaders.DISTRIBUTION_INDICATOR:
+                case PduHeaders.QUOTAS:
+                case PduHeaders.READ_REPORT:
+                case PduHeaders.STORE:
+                case PduHeaders.STORED:
+                case PduHeaders.TOTALS:
+                case PduHeaders.SENDER_VISIBILITY:
+                case PduHeaders.READ_STATUS:
+                case PduHeaders.CANCEL_STATUS:
+                case PduHeaders.PRIORITY:
+                case PduHeaders.STATUS:
+                case PduHeaders.REPLY_CHARGING:
+                case PduHeaders.MM_STATE:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE:
+                case PduHeaders.CONTENT_CLASS:
+                case PduHeaders.RETRIEVE_STATUS:
+                case PduHeaders.STORE_STATUS:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.RESPONSE_STATUS:
+                {
+                    int value = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: byte: " + headerField + " value: " +
+                                value);
+                    }
+
+                    try {
+                        headers.setOctet(value, headerField);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + value +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Long-Integer */
+                case PduHeaders.DATE:
+                case PduHeaders.REPLY_CHARGING_SIZE:
+                case PduHeaders.MESSAGE_SIZE:
+                {
+                    try {
+                        long value = parseLongInteger(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: longint: " + headerField + " value: " +
+                                    value);
+                        }
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Integer-Value */
+                case PduHeaders.MESSAGE_COUNT:
+                case PduHeaders.START:
+                case PduHeaders.LIMIT:
+                {
+                    try {
+                        long value = parseIntegerValue(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: int: " + headerField + " value: " +
+                                    value);
+                        }
+                        headers.setLongInteger(value, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                /* Text-String */
+                case PduHeaders.TRANSACTION_ID:
+                case PduHeaders.REPLY_CHARGING_ID:
+                case PduHeaders.AUX_APPLIC_ID:
+                case PduHeaders.APPLIC_ID:
+                case PduHeaders.REPLY_APPLIC_ID:
+                    /**
+                     * The next three header fields are email addresses
+                     * as defined in RFC2822,
+                     * not including the characters "<" and ">"
+                     */
+                case PduHeaders.MESSAGE_ID:
+                case PduHeaders.REPLACE_ID:
+                case PduHeaders.CANCEL_ID:
+                    /**
+                     * The following field has a different value when
+                     * used in the M-Mbox-Delete.conf and M-Delete.conf PDU.
+                     * For now we ignore this fact, since we do not support these PDUs
+                     */
+                case PduHeaders.CONTENT_LOCATION:
+                {
+                    byte[] value = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if (null != value) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: string: " + headerField + " value: " +
+                                        new String(value));
+                            }
+                            headers.setTextString(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Encoded-string-value */
+                case PduHeaders.SUBJECT:
+                case PduHeaders.RECOMMENDED_RETRIEVAL_MODE_TEXT:
+                case PduHeaders.RETRIEVE_TEXT:
+                case PduHeaders.STATUS_TEXT:
+                case PduHeaders.STORE_STATUS_TEXT:
+                    /* the next one is not support
+                     * M-Mbox-Delete.conf and M-Delete.conf now */
+                case PduHeaders.RESPONSE_TEXT:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: encoded string: " + headerField
+                                        + " value: " + value.getString());
+                            }
+                            headers.setEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch (RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Addressing model */
+                case PduHeaders.BCC:
+                case PduHeaders.CC:
+                case PduHeaders.TO:
+                {
+                    EncodedStringValue value =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != value) {
+                        byte[] address = value.getTextString();
+                        if (null != address) {
+                            String str = new String(address);
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: (to/cc/bcc) address: " + headerField
+                                        + " value: " + str);
+                            }
+                            int endIndex = str.indexOf("/");
+                            if (endIndex > 0) {
+                                str = str.substring(0, endIndex);
+                            }
+                            try {
+                                value.setTextString(str.getBytes());
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                                return null;
+                            }
+                        }
+
+                        try {
+                            headers.appendEncodedStringValue(value, headerField);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                /* Value-length
+                 * (Absolute-token Date-value | Relative-token Delta-seconds-value) */
+                case PduHeaders.DELIVERY_TIME:
+                case PduHeaders.EXPIRY:
+                case PduHeaders.REPLY_CHARGING_DEADLINE:
+                {
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Absolute-token or Relative-token */
+                    int token = extractByteValue(pduDataStream);
+
+                    /* Date-value or Delta-seconds-value */
+                    long timeValue;
+                    try {
+                        timeValue = parseLongInteger(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    if (PduHeaders.VALUE_RELATIVE_TOKEN == token) {
+                        /* need to convert the Delta-seconds-value
+                         * into Date-value */
+                        timeValue = System.currentTimeMillis()/1000 + timeValue;
+                    }
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: time value: " + headerField
+                                    + " value: " + timeValue);
+                        }
+                        headers.setLongInteger(timeValue, headerField);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.FROM: {
+                    /* From-value =
+                     * Value-length
+                     * (Address-present-token Encoded-string-value | Insert-address-token)
+                     */
+                    EncodedStringValue from = null;
+                    parseValueLength(pduDataStream); /* parse value-length */
+
+                    /* Address-present-token or Insert-address-token */
+                    int fromToken = extractByteValue(pduDataStream);
+
+                    /* Address-present-token or Insert-address-token */
+                    if (PduHeaders.FROM_ADDRESS_PRESENT_TOKEN == fromToken) {
+                        /* Encoded-string-value */
+                        from = parseEncodedStringValue(pduDataStream);
+                        if (null != from) {
+                            byte[] address = from.getTextString();
+                            if (null != address) {
+                                String str = new String(address);
+                                int endIndex = str.indexOf("/");
+                                if (endIndex > 0) {
+                                    str = str.substring(0, endIndex);
+                                }
+                                try {
+                                    from.setTextString(str.getBytes());
+                                } catch(NullPointerException e) {
+                                    log("null pointer error!");
+                                    return null;
+                                }
+                            }
+                        }
+                    } else {
+                        try {
+                            from = new EncodedStringValue(
+                                    PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes());
+                        } catch(NullPointerException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: from address: " + headerField
+                                    + " value: " + from.getString());
+                        }
+                        headers.setEncodedStringValue(from, PduHeaders.FROM);
+                    } catch(NullPointerException e) {
+                        log("null pointer error!");
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Encoded-String-Value header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MESSAGE_CLASS: {
+                    /* Message-class-value = Class-identifier | Token-text */
+                    pduDataStream.mark(1);
+                    int messageClass = extractByteValue(pduDataStream);
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MESSAGE_CLASS: " + headerField
+                                + " value: " + messageClass);
+                    }
+
+                    if (messageClass >= PduHeaders.MESSAGE_CLASS_PERSONAL) {
+                        /* Class-identifier */
+                        try {
+                            if (PduHeaders.MESSAGE_CLASS_PERSONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_ADVERTISEMENT == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_ADVERTISEMENT_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_INFORMATIONAL == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_INFORMATIONAL_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            } else if (PduHeaders.MESSAGE_CLASS_AUTO == messageClass) {
+                                headers.setTextString(
+                                        PduHeaders.MESSAGE_CLASS_AUTO_STR.getBytes(),
+                                        PduHeaders.MESSAGE_CLASS);
+                            }
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    } else {
+                        /* Token-text */
+                        pduDataStream.reset();
+                        byte[] messageClassString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != messageClassString) {
+                            try {
+                                headers.setTextString(messageClassString, PduHeaders.MESSAGE_CLASS);
+                            } catch(NullPointerException e) {
+                                log("null pointer error!");
+                            } catch(RuntimeException e) {
+                                log(headerField + "is not Text-String header field!");
+                                return null;
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.MMS_VERSION: {
+                    int version = parseShortInteger(pduDataStream);
+
+                    try {
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: MMS_VERSION: " + headerField
+                                    + " value: " + version);
+                        }
+                        headers.setOctet(version, PduHeaders.MMS_VERSION);
+                    } catch(InvalidHeaderValueException e) {
+                        log("Set invalid Octet value: " + version +
+                                " into the header filed: " + headerField);
+                        return null;
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Octet header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_BY: {
+                    /* Previously-sent-by-value =
+                     * Value-length Forwarded-count-value Encoded-string-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* parse Encoded-string-value */
+                    EncodedStringValue previouslySentBy =
+                        parseEncodedStringValue(pduDataStream);
+                    if (null != previouslySentBy) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_BY: " + headerField
+                                        + " value: " + previouslySentBy.getString());
+                            }
+                            headers.setEncodedStringValue(previouslySentBy,
+                                    PduHeaders.PREVIOUSLY_SENT_BY);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Encoded-String-Value header field!");
+                            return null;
+                        }
+                    }
+                    break;
+                }
+
+                case PduHeaders.PREVIOUSLY_SENT_DATE: {
+                    /* Previously-sent-date-value =
+                     * Value-length Forwarded-count-value Date-value */
+                    /* parse value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* parse Forwarded-count-value */
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* Date-value */
+                    try {
+                        long perviouslySentDate = parseLongInteger(pduDataStream);
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "parseHeaders: PREVIOUSLY_SENT_DATE: " + headerField
+                                    + " value: " + perviouslySentDate);
+                        }
+                        headers.setLongInteger(perviouslySentDate,
+                                PduHeaders.PREVIOUSLY_SENT_DATE);
+                    } catch(RuntimeException e) {
+                        log(headerField + "is not Long-Integer header field!");
+                        return null;
+                    }
+                    break;
+                }
+
+                case PduHeaders.MM_FLAGS: {
+                    /* MM-flags-value =
+                     * Value-length
+                     * ( Add-token | Remove-token | Filter-token )
+                     * Encoded-string-value
+                     */
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MM_FLAGS: " + headerField
+                                + " NOT REALLY SUPPORTED");
+                    }
+
+                    /* parse Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Add-token | Remove-token | Filter-token */
+                    extractByteValue(pduDataStream);
+
+                    /* Encoded-string-value */
+                    parseEncodedStringValue(pduDataStream);
+
+                    /* not store this header filed in "headers",
+                     * because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                /* Value-length
+                 * (Message-total-token | Size-total-token) Integer-Value */
+                case PduHeaders.MBOX_TOTALS:
+                case PduHeaders.MBOX_QUOTAS:
+                {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: MBOX_TOTALS: " + headerField);
+                    }
+                    /* Value-length */
+                    parseValueLength(pduDataStream);
+
+                    /* Message-total-token | Size-total-token */
+                    extractByteValue(pduDataStream);
+
+                    /*Integer-Value*/
+                    try {
+                        parseIntegerValue(pduDataStream);
+                    } catch(RuntimeException e) {
+                        log(headerField + " is not Integer-Value");
+                        return null;
+                    }
+
+                    /* not store these headers filed in "headers",
+                    because now PduHeaders doesn't support them */
+                    break;
+                }
+
+                case PduHeaders.ELEMENT_DESCRIPTOR: {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: ELEMENT_DESCRIPTOR: " + headerField);
+                    }
+                    parseContentType(pduDataStream, null);
+
+                    /* not store this header filed in "headers",
+                    because now PduHeaders doesn't support it */
+                    break;
+                }
+
+                case PduHeaders.CONTENT_TYPE: {
+                    HashMap<Integer, Object> map =
+                        new HashMap<Integer, Object>();
+                    byte[] contentType =
+                        parseContentType(pduDataStream, map);
+
+                    if (null != contentType) {
+                        try {
+                            if (LOCAL_LOGV) {
+                                Log.v(LOG_TAG, "parseHeaders: CONTENT_TYPE: " + headerField +
+                                        contentType.toString());
+                            }
+                            headers.setTextString(contentType, PduHeaders.CONTENT_TYPE);
+                        } catch(NullPointerException e) {
+                            log("null pointer error!");
+                        } catch(RuntimeException e) {
+                            log(headerField + "is not Text-String header field!");
+                            return null;
+                        }
+                    }
+
+                    /* get start parameter */
+                    mStartParam = (byte[]) map.get(PduPart.P_START);
+
+                    /* get charset parameter */
+                    mTypeParam= (byte[]) map.get(PduPart.P_TYPE);
+
+                    keepParsing = false;
+                    break;
+                }
+
+                case PduHeaders.CONTENT:
+                case PduHeaders.ADDITIONAL_HEADERS:
+                case PduHeaders.ATTRIBUTES:
+                default: {
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "parseHeaders: Unknown header: " + headerField);
+                    }
+                    log("Unknown header");
+                }
+            }
+        }
+
+        return headers;
+    }
+
+    /**
+     * Parse pdu parts.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return parts in PduBody structure
+     */
+    protected PduBody parseParts(ByteArrayInputStream pduDataStream) {
+        if (pduDataStream == null) {
+            return null;
+        }
+
+        int count = parseUnsignedInt(pduDataStream); // get the number of parts
+        PduBody body = new PduBody();
+
+        for (int i = 0 ; i < count ; i++) {
+            int headerLength = parseUnsignedInt(pduDataStream);
+            int dataLength = parseUnsignedInt(pduDataStream);
+            PduPart part = new PduPart();
+            int startPos = pduDataStream.available();
+            if (startPos <= 0) {
+                // Invalid part.
+                return null;
+            }
+
+            /* parse part's content-type */
+            HashMap<Integer, Object> map = new HashMap<Integer, Object>();
+            byte[] contentType = parseContentType(pduDataStream, map);
+            if (null != contentType) {
+                part.setContentType(contentType);
+            } else {
+                part.setContentType((PduContentTypes.contentTypes[0]).getBytes()); //"*/*"
+            }
+
+            /* get name parameter */
+            byte[] name = (byte[]) map.get(PduPart.P_NAME);
+            if (null != name) {
+                part.setName(name);
+            }
+
+            /* get charset parameter */
+            Integer charset = (Integer) map.get(PduPart.P_CHARSET);
+            if (null != charset) {
+                part.setCharset(charset);
+            }
+
+            /* parse part's headers */
+            int endPos = pduDataStream.available();
+            int partHeaderLen = headerLength - (startPos - endPos);
+            if (partHeaderLen > 0) {
+                if (false == parsePartHeaders(pduDataStream, part, partHeaderLen)) {
+                    // Parse part header faild.
+                    return null;
+                }
+            } else if (partHeaderLen < 0) {
+                // Invalid length of content-type.
+                return null;
+            }
+
+            /* FIXME: check content-id, name, filename and content location,
+             * if not set anyone of them, generate a default content-location
+             */
+            if ((null == part.getContentLocation())
+                    && (null == part.getName())
+                    && (null == part.getFilename())
+                    && (null == part.getContentId())) {
+                part.setContentLocation(Long.toOctalString(
+                        System.currentTimeMillis()).getBytes());
+            }
+
+            /* get part's data */
+            if (dataLength > 0) {
+                byte[] partData = new byte[dataLength];
+                String partContentType = new String(part.getContentType());
+                pduDataStream.read(partData, 0, dataLength);
+                if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
+                    // parse "multipart/vnd.wap.multipart.alternative".
+                    PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+                    // take the first part of children.
+                    part = childBody.getPart(0);
+                } else {
+                    // Check Content-Transfer-Encoding.
+                    byte[] partDataEncoding = part.getContentTransferEncoding();
+                    if (null != partDataEncoding) {
+                        String encoding = new String(partDataEncoding);
+                        if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+                            // Decode "base64" into "binary".
+                            partData = Base64.decodeBase64(partData);
+                        } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+                            // Decode "quoted-printable" into "binary".
+                            partData = QuotedPrintable.decodeQuotedPrintable(partData);
+                        } else {
+                            // "binary" is the default encoding.
+                        }
+                    }
+                    if (null == partData) {
+                        log("Decode part data error!");
+                        return null;
+                    }
+                    part.setData(partData);
+                }
+            }
+
+            /* add this part to body */
+            if (THE_FIRST_PART == checkPartPosition(part)) {
+                /* this is the first part */
+                body.addPart(0, part);
+            } else {
+                /* add the part to the end */
+                body.addPart(part);
+            }
+        }
+
+        return body;
+    }
+
+    /**
+     * Log status.
+     *
+     * @param text log information
+     */
+    @UnsupportedAppUsage
+    private static void log(String text) {
+        if (LOCAL_LOGV) {
+            Log.v(LOG_TAG, text);
+        }
+    }
+
+    /**
+     * Parse unsigned integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer, -1 when failed
+     */
+    @UnsupportedAppUsage
+    protected static int parseUnsignedInt(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * The maximum size of a uintvar is 32 bits.
+         * So it will be encoded in no more than 5 octets.
+         */
+        assert(null != pduDataStream);
+        int result = 0;
+        int temp = pduDataStream.read();
+        if (temp == -1) {
+            return temp;
+        }
+
+        while((temp & 0x80) != 0) {
+            result = result << 7;
+            result |= temp & 0x7F;
+            temp = pduDataStream.read();
+            if (temp == -1) {
+                return temp;
+            }
+        }
+
+        result = result << 7;
+        result |= temp & 0x7F;
+
+        return result;
+    }
+
+    /**
+     * Parse value length.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the integer
+     */
+    @UnsupportedAppUsage
+    protected static int parseValueLength(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Value-length = Short-length | (Length-quote Length)
+         * Short-length = <Any octet 0-30>
+         * Length-quote = <Octet 31>
+         * Length = Uintvar-integer
+         * Uintvar-integer = 1*5 OCTET
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+
+        if (first <= SHORT_LENGTH_MAX) {
+            return first;
+        } else if (first == LENGTH_QUOTE) {
+            return parseUnsignedInt(pduDataStream);
+        }
+
+        throw new RuntimeException ("Value length > LENGTH_QUOTE!");
+    }
+
+    /**
+     * Parse encoded string value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the EncodedStringValue
+     */
+    protected static EncodedStringValue parseEncodedStringValue(ByteArrayInputStream pduDataStream){
+        /**
+         * From OMA-TS-MMS-ENC-V1_3-20050927-C.pdf
+         * Encoded-string-value = Text-string | Value-length Char-set Text-string
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        EncodedStringValue returnValue = null;
+        int charset = 0;
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int first = temp & 0xFF;
+        if (first == 0) {
+            return new EncodedStringValue("");
+        }
+
+        pduDataStream.reset();
+        if (first < TEXT_MIN) {
+            parseValueLength(pduDataStream);
+
+            charset = parseShortInteger(pduDataStream); //get the "Charset"
+        }
+
+        byte[] textString = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+        try {
+            if (0 != charset) {
+                returnValue = new EncodedStringValue(charset, textString);
+            } else {
+                returnValue = new EncodedStringValue(textString);
+            }
+        } catch(Exception e) {
+            return null;
+        }
+
+        return returnValue;
+    }
+
+    /**
+     * Parse Text-String or Quoted-String.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param stringType TYPE_TEXT_STRING or TYPE_QUOTED_STRING
+     * @return the string without End-of-string in byte array
+     */
+    @UnsupportedAppUsage
+    protected static byte[] parseWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Text-string = [Quote] *TEXT End-of-string
+         * If the first character in the TEXT is in the range of 128-255,
+         * a Quote character must precede it.
+         * Otherwise the Quote character must be omitted.
+         * The Quote is not part of the contents.
+         * Quote = <Octet 127>
+         * End-of-string = <Octet 0>
+         *
+         * Quoted-string = <Octet 34> *TEXT End-of-string
+         *
+         * Token-text = Token End-of-string
+         */
+
+        // Mark supposed beginning of Text-string
+        // We will have to mark again if first char is QUOTE or QUOTED_STRING_FLAG
+        pduDataStream.mark(1);
+
+        // Check first char
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        if ((TYPE_QUOTED_STRING == stringType) &&
+                (QUOTED_STRING_FLAG == temp)) {
+            // Mark again if QUOTED_STRING_FLAG and ignore it
+            pduDataStream.mark(1);
+        } else if ((TYPE_TEXT_STRING == stringType) &&
+                (QUOTE == temp)) {
+            // Mark again if QUOTE and ignore it
+            pduDataStream.mark(1);
+        } else {
+            // Otherwise go back to origin
+            pduDataStream.reset();
+        }
+
+        // We are now definitely at the beginning of string
+        /**
+         * Return *TOKEN or *TEXT (Text-String without QUOTE,
+         * Quoted-String without QUOTED_STRING_FLAG and without End-of-string)
+         */
+        return getWapString(pduDataStream, stringType);
+    }
+
+    /**
+     * Check TOKEN data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TOKEN, false when ch is not TOKEN
+     */
+    protected static boolean isTokenCharacter(int ch) {
+        /**
+         * Token      = 1*<any CHAR except CTLs or separators>
+         * separators = "("(40) | ")"(41) | "<"(60) | ">"(62) | "@"(64)
+         *            | ","(44) | ";"(59) | ":"(58) | "\"(92) | <">(34)
+         *            | "/"(47) | "["(91) | "]"(93) | "?"(63) | "="(61)
+         *            | "{"(123) | "}"(125) | SP(32) | HT(9)
+         * CHAR       = <any US-ASCII character (octets 0 - 127)>
+         * CTL        = <any US-ASCII control character
+         *            (octets 0 - 31) and DEL (127)>
+         * SP         = <US-ASCII SP, space (32)>
+         * HT         = <US-ASCII HT, horizontal-tab (9)>
+         */
+        if((ch < 33) || (ch > 126)) {
+            return false;
+        }
+
+        switch(ch) {
+            case '"': /* '"' */
+            case '(': /* '(' */
+            case ')': /* ')' */
+            case ',': /* ',' */
+            case '/': /* '/' */
+            case ':': /* ':' */
+            case ';': /* ';' */
+            case '<': /* '<' */
+            case '=': /* '=' */
+            case '>': /* '>' */
+            case '?': /* '?' */
+            case '@': /* '@' */
+            case '[': /* '[' */
+            case '\\': /* '\' */
+            case ']': /* ']' */
+            case '{': /* '{' */
+            case '}': /* '}' */
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check TEXT data defined in RFC2616.
+     * @param ch checking data
+     * @return true when ch is TEXT, false when ch is not TEXT
+     */
+    protected static boolean isText(int ch) {
+        /**
+         * TEXT = <any OCTET except CTLs,
+         *      but including LWS>
+         * CTL  = <any US-ASCII control character
+         *      (octets 0 - 31) and DEL (127)>
+         * LWS  = [CRLF] 1*( SP | HT )
+         * CRLF = CR LF
+         * CR   = <US-ASCII CR, carriage return (13)>
+         * LF   = <US-ASCII LF, linefeed (10)>
+         */
+        if(((ch >= 32) && (ch <= 126)) || ((ch >= 128) && (ch <= 255))) {
+            return true;
+        }
+
+        switch(ch) {
+            case '\t': /* '\t' */
+            case '\n': /* '\n' */
+            case '\r': /* '\r' */
+                return true;
+        }
+
+        return false;
+    }
+
+    protected static byte[] getWapString(ByteArrayInputStream pduDataStream,
+            int stringType) {
+        assert(null != pduDataStream);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        while((-1 != temp) && ('\0' != temp)) {
+            // check each of the character
+            if (stringType == TYPE_TOKEN_STRING) {
+                if (isTokenCharacter(temp)) {
+                    out.write(temp);
+                }
+            } else {
+                if (isText(temp)) {
+                    out.write(temp);
+                }
+            }
+
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+        }
+
+        if (out.size() > 0) {
+            return out.toByteArray();
+        }
+
+        return null;
+    }
+
+    /**
+     * Extract a byte value from the input stream.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    protected static int extractByteValue(ByteArrayInputStream pduDataStream) {
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0xFF;
+    }
+
+    /**
+     * Parse Short-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return the byte
+     */
+    @UnsupportedAppUsage
+    protected static int parseShortInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Short-integer = OCTET
+         * Integers in range 0-127 shall be encoded as a one
+         * octet value with the most significant bit set to one (1xxx xxxx)
+         * and with the value in the remaining least significant bits.
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        return temp & 0x7F;
+    }
+
+    /**
+     * Parse Long-Integer.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseLongInteger(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Long-integer = Short-length Multi-octet-integer
+         * The Short-length indicates the length of the Multi-octet-integer
+         * Multi-octet-integer = 1*30 OCTET
+         * The content octets shall be an unsigned integer value
+         * with the most significant octet encoded first (big-endian representation).
+         * The minimum number of octets must be used to encode the value.
+         * Short-length = <Any octet 0-30>
+         */
+        assert(null != pduDataStream);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        int count = temp & 0xFF;
+
+        if (count > LONG_INTEGER_LENGTH_MAX) {
+            throw new RuntimeException("Octet count greater than 8 and I can't represent that!");
+        }
+
+        long result = 0;
+
+        for (int i = 0 ; i < count ; i++) {
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            result <<= 8;
+            result += (temp & 0xFF);
+        }
+
+        return result;
+    }
+
+    /**
+     * Parse Integer-Value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @return long integer
+     */
+    protected static long parseIntegerValue(ByteArrayInputStream pduDataStream) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Integer-Value = Short-integer | Long-integer
+         */
+        assert(null != pduDataStream);
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+        if (temp > SHORT_INTEGER_MAX) {
+            return parseShortInteger(pduDataStream);
+        } else {
+            return parseLongInteger(pduDataStream);
+        }
+    }
+
+    /**
+     * To skip length of the wap value.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param length area size
+     * @return the values in this area
+     */
+    protected static int skipWapValue(ByteArrayInputStream pduDataStream, int length) {
+        assert(null != pduDataStream);
+        byte[] area = new byte[length];
+        int readLen = pduDataStream.read(area, 0, length);
+        if (readLen < length) { //The actually read length is lower than the length
+            return -1;
+        } else {
+            return readLen;
+        }
+    }
+
+    /**
+     * Parse content type parameters. For now we just support
+     * four parameters used in mms: "type", "start", "name", "charset".
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters of Content-Type field
+     * @param length length of all the parameters
+     */
+    protected static void parseContentTypeParams(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map, Integer length) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Parameter = Typed-parameter | Untyped-parameter
+         * Typed-parameter = Well-known-parameter-token Typed-value
+         * the actual expected type of the value is implied by the well-known parameter
+         * Well-known-parameter-token = Integer-value
+         * the code values used for parameters are specified in the Assigned Numbers appendix
+         * Typed-value = Compact-value | Text-value
+         * In addition to the expected type, there may be no value.
+         * If the value cannot be encoded using the expected type, it shall be encoded as text.
+         * Compact-value = Integer-value |
+         * Date-value | Delta-seconds-value | Q-value | Version-value |
+         * Uri-value
+         * Untyped-parameter = Token-text Untyped-value
+         * the type of the value is unknown, but it shall be encoded as an integer,
+         * if that is possible.
+         * Untyped-value = Integer-value | Text-value
+         */
+        assert(null != pduDataStream);
+        assert(length > 0);
+
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int param = pduDataStream.read();
+            assert(-1 != param);
+            lastLen--;
+
+            switch (param) {
+                /**
+                 * From rfc2387, chapter 3.1
+                 * The type parameter must be specified and its value is the MIME media
+                 * type of the "root" body part. It permits a MIME user agent to
+                 * determine the content-type without reference to the enclosed body
+                 * part. If the value of the type parameter and the root body part's
+                 * content-type differ then the User Agent's behavior is undefined.
+                 *
+                 * From wap-230-wsp-20010705-a.pdf
+                 * type = Constrained-encoding
+                 * Constrained-encoding = Extension-Media | Short-integer
+                 * Extension-media = *TEXT End-of-string
+                 */
+                case PduPart.P_TYPE:
+                case PduPart.P_CT_MR_TYPE:
+                    pduDataStream.mark(1);
+                    int first = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    if (first > TEXT_MAX) {
+                        // Short-integer (well-known type)
+                        int index = parseShortInteger(pduDataStream);
+
+                        if (index < PduContentTypes.contentTypes.length) {
+                            byte[] type = (PduContentTypes.contentTypes[index]).getBytes();
+                            map.put(PduPart.P_TYPE, type);
+                        } else {
+                            //not support this type, ignore it.
+                        }
+                    } else {
+                        // Text-String (extension-media)
+                        byte[] type = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if ((null != type) && (null != map)) {
+                            map.put(PduPart.P_TYPE, type);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.3.
+                     * Start Parameter Referring to Presentation
+                     *
+                     * From rfc2387, chapter 3.2
+                     * The start parameter, if given, is the content-ID of the compound
+                     * object's "root". If not present the "root" is the first body part in
+                     * the Multipart/Related entity. The "root" is the element the
+                     * applications processes first.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * start = Text-String
+                     */
+                case PduPart.P_START:
+                case PduPart.P_DEP_START:
+                    byte[] start = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != start) && (null != map)) {
+                        map.put(PduPart.P_START, start);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * In creation, the character set SHALL be either us-ascii
+                     * (IANA MIBenum 3) or utf-8 (IANA MIBenum 106)[Unicode].
+                     * In retrieval, both us-ascii and utf-8 SHALL be supported.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * charset = Well-known-charset|Text-String
+                     * Well-known-charset = Any-charset | Integer-value
+                     * Both are encoded using values from Character Set
+                     * Assignments table in Assigned Numbers
+                     * Any-charset = <Octet 128>
+                     * Equivalent to the special RFC2616 charset value "*"
+                     */
+                case PduPart.P_CHARSET:
+                    pduDataStream.mark(1);
+                    int firstValue = extractByteValue(pduDataStream);
+                    pduDataStream.reset();
+                    //Check first char
+                    if (((firstValue > TEXT_MIN) && (firstValue < TEXT_MAX)) ||
+                            (END_STRING_FLAG == firstValue)) {
+                        //Text-String (extension-charset)
+                        byte[] charsetStr = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        try {
+                            int charsetInt = CharacterSets.getMibEnumValue(
+                                    new String(charsetStr));
+                            map.put(PduPart.P_CHARSET, charsetInt);
+                        } catch (UnsupportedEncodingException e) {
+                            // Not a well-known charset, use "*".
+                            Log.e(LOG_TAG, Arrays.toString(charsetStr), e);
+                            map.put(PduPart.P_CHARSET, CharacterSets.ANY_CHARSET);
+                        }
+                    } else {
+                        //Well-known-charset
+                        int charset = (int) parseIntegerValue(pduDataStream);
+                        if (map != null) {
+                            map.put(PduPart.P_CHARSET, charset);
+                        }
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+
+                    /**
+                     * From oma-ts-mms-conf-v1_3.pdf
+                     * A name for multipart object SHALL be encoded using name-parameter
+                     * for Content-Type header in WSP multipart headers.
+                     *
+                     * From wap-230-wsp-20010705-a.pdf
+                     * name = Text-String
+                     */
+                case PduPart.P_DEP_NAME:
+                case PduPart.P_NAME:
+                    byte[] name = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                    if ((null != name) && (null != map)) {
+                        map.put(PduPart.P_NAME, name);
+                    }
+
+                    tempPos = pduDataStream.available();
+                    lastLen = length - (startPos - tempPos);
+                    break;
+                default:
+                    if (LOCAL_LOGV) {
+                        Log.v(LOG_TAG, "Not supported Content-Type parameter");
+                    }
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Content-Type");
+                } else {
+                    lastLen = 0;
+                }
+                break;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Content-Type");
+        }
+    }
+
+    /**
+     * Parse content type.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param map to store parameters in Content-Type header field
+     * @return Content-Type value
+     */
+    @UnsupportedAppUsage
+    protected static byte[] parseContentType(ByteArrayInputStream pduDataStream,
+            HashMap<Integer, Object> map) {
+        /**
+         * From wap-230-wsp-20010705-a.pdf
+         * Content-type-value = Constrained-media | Content-general-form
+         * Content-general-form = Value-length Media-type
+         * Media-type = (Well-known-media | Extension-Media) *(Parameter)
+         */
+        assert(null != pduDataStream);
+
+        byte[] contentType = null;
+        pduDataStream.mark(1);
+        int temp = pduDataStream.read();
+        assert(-1 != temp);
+        pduDataStream.reset();
+
+        int cur = (temp & 0xFF);
+
+        if (cur < TEXT_MIN) {
+            int length = parseValueLength(pduDataStream);
+            int startPos = pduDataStream.available();
+            pduDataStream.mark(1);
+            temp = pduDataStream.read();
+            assert(-1 != temp);
+            pduDataStream.reset();
+            int first = (temp & 0xFF);
+
+            if ((first >= TEXT_MIN) && (first <= TEXT_MAX)) {
+                contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+            } else if (first > TEXT_MAX) {
+                int index = parseShortInteger(pduDataStream);
+
+                if (index < PduContentTypes.contentTypes.length) { //well-known type
+                    contentType = (PduContentTypes.contentTypes[index]).getBytes();
+                } else {
+                    pduDataStream.reset();
+                    contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                }
+            } else {
+                Log.e(LOG_TAG, "Corrupt content-type");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+
+            int endPos = pduDataStream.available();
+            int parameterLen = length - (startPos - endPos);
+            if (parameterLen > 0) {//have parameters
+                parseContentTypeParams(pduDataStream, map, parameterLen);
+            }
+
+            if (parameterLen < 0) {
+                Log.e(LOG_TAG, "Corrupt MMS message");
+                return (PduContentTypes.contentTypes[0]).getBytes(); //"*/*"
+            }
+        } else if (cur <= TEXT_MAX) {
+            contentType = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+        } else {
+            contentType =
+                (PduContentTypes.contentTypes[parseShortInteger(pduDataStream)]).getBytes();
+        }
+
+        return contentType;
+    }
+
+    /**
+     * Parse part's headers.
+     *
+     * @param pduDataStream pdu data input stream
+     * @param part to store the header informations of the part
+     * @param length length of the headers
+     * @return true if parse successfully, false otherwise
+     */
+    @UnsupportedAppUsage
+    protected boolean parsePartHeaders(ByteArrayInputStream pduDataStream,
+            PduPart part, int length) {
+        assert(null != pduDataStream);
+        assert(null != part);
+        assert(length > 0);
+
+        /**
+         * From oma-ts-mms-conf-v1_3.pdf, chapter 10.2.
+         * A name for multipart object SHALL be encoded using name-parameter
+         * for Content-Type header in WSP multipart headers.
+         * In decoding, name-parameter of Content-Type SHALL be used if available.
+         * If name-parameter of Content-Type is not available,
+         * filename parameter of Content-Disposition header SHALL be used if available.
+         * If neither name-parameter of Content-Type header nor filename parameter
+         * of Content-Disposition header is available,
+         * Content-Location header SHALL be used if available.
+         *
+         * Within SMIL part the reference to the media object parts SHALL use
+         * either Content-ID or Content-Location mechanism [RFC2557]
+         * and the corresponding WSP part headers in media object parts
+         * contain the corresponding definitions.
+         */
+        int startPos = pduDataStream.available();
+        int tempPos = 0;
+        int lastLen = length;
+        while(0 < lastLen) {
+            int header = pduDataStream.read();
+            assert(-1 != header);
+            lastLen--;
+
+            if (header > TEXT_MAX) {
+                // Number assigned headers.
+                switch (header) {
+                    case PduPart.P_CONTENT_LOCATION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-location-value = Uri-value
+                         */
+                        byte[] contentLocation = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                        if (null != contentLocation) {
+                            part.setContentLocation(contentLocation);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_CONTENT_ID:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-ID-value = Quoted-string
+                         */
+                        byte[] contentId = parseWapString(pduDataStream, TYPE_QUOTED_STRING);
+                        if (null != contentId) {
+                            part.setContentId(contentId);
+                        }
+
+                        tempPos = pduDataStream.available();
+                        lastLen = length - (startPos - tempPos);
+                        break;
+                    case PduPart.P_DEP_CONTENT_DISPOSITION:
+                    case PduPart.P_CONTENT_DISPOSITION:
+                        /**
+                         * From wap-230-wsp-20010705-a.pdf, chapter 8.4.2.21
+                         * Content-disposition-value = Value-length Disposition *(Parameter)
+                         * Disposition = Form-data | Attachment | Inline | Token-text
+                         * Form-data = <Octet 128>
+                         * Attachment = <Octet 129>
+                         * Inline = <Octet 130>
+                         */
+
+                        /*
+                         * some carrier mmsc servers do not support content_disposition
+                         * field correctly
+                         */
+                        if (mParseContentDisposition) {
+                            int len = parseValueLength(pduDataStream);
+                            pduDataStream.mark(1);
+                            int thisStartPos = pduDataStream.available();
+                            int thisEndPos = 0;
+                            int value = pduDataStream.read();
+
+                            if (value == PduPart.P_DISPOSITION_FROM_DATA ) {
+                                part.setContentDisposition(PduPart.DISPOSITION_FROM_DATA);
+                            } else if (value == PduPart.P_DISPOSITION_ATTACHMENT) {
+                                part.setContentDisposition(PduPart.DISPOSITION_ATTACHMENT);
+                            } else if (value == PduPart.P_DISPOSITION_INLINE) {
+                                part.setContentDisposition(PduPart.DISPOSITION_INLINE);
+                            } else {
+                                pduDataStream.reset();
+                                /* Token-text */
+                                part.setContentDisposition(parseWapString(pduDataStream
+                                        , TYPE_TEXT_STRING));
+                            }
+
+                            /* get filename parameter and skip other parameters */
+                            thisEndPos = pduDataStream.available();
+                            if (thisStartPos - thisEndPos < len) {
+                                value = pduDataStream.read();
+                                if (value == PduPart.P_FILENAME) { //filename is text-string
+                                    part.setFilename(parseWapString(pduDataStream
+                                            , TYPE_TEXT_STRING));
+                                }
+
+                                /* skip other parameters */
+                                thisEndPos = pduDataStream.available();
+                                if (thisStartPos - thisEndPos < len) {
+                                    int last = len - (thisStartPos - thisEndPos);
+                                    byte[] temp = new byte[last];
+                                    pduDataStream.read(temp, 0, last);
+                                }
+                            }
+
+                            tempPos = pduDataStream.available();
+                            lastLen = length - (startPos - tempPos);
+                        }
+                        break;
+                    default:
+                        if (LOCAL_LOGV) {
+                            Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                        }
+                    if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                        Log.e(LOG_TAG, "Corrupt Part headers");
+                        return false;
+                    }
+                    lastLen = 0;
+                    break;
+                }
+            } else if ((header >= TEXT_MIN) && (header <= TEXT_MAX)) {
+                // Not assigned header.
+                byte[] tempHeader = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+                byte[] tempValue = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+
+                // Check the header whether it is "Content-Transfer-Encoding".
+                if (true ==
+                    PduPart.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(new String(tempHeader))) {
+                    part.setContentTransferEncoding(tempValue);
+                }
+
+                tempPos = pduDataStream.available();
+                lastLen = length - (startPos - tempPos);
+            } else {
+                if (LOCAL_LOGV) {
+                    Log.v(LOG_TAG, "Not supported Part headers: " + header);
+                }
+                // Skip all headers of this part.
+                if (-1 == skipWapValue(pduDataStream, lastLen)) {
+                    Log.e(LOG_TAG, "Corrupt Part headers");
+                    return false;
+                }
+                lastLen = 0;
+            }
+        }
+
+        if (0 != lastLen) {
+            Log.e(LOG_TAG, "Corrupt Part headers");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check the position of a specified part.
+     *
+     * @param part the part to be checked
+     * @return part position, THE_FIRST_PART when it's the
+     * first one, THE_LAST_PART when it's the last one.
+     */
+    @UnsupportedAppUsage
+    private static int checkPartPosition(PduPart part) {
+        assert(null != part);
+        if ((null == mTypeParam) &&
+                (null == mStartParam)) {
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-id */
+        if (null != mStartParam) {
+            byte[] contentId = part.getContentId();
+            if (null != contentId) {
+                if (true == Arrays.equals(mStartParam, contentId)) {
+                    return THE_FIRST_PART;
+                }
+            }
+            // This is not the first part, so append to end (keeping the original order)
+            // Check b/19607294 for details of this change
+            return THE_LAST_PART;
+        }
+
+        /* check part's content-type */
+        if (null != mTypeParam) {
+            byte[] contentType = part.getContentType();
+            if (null != contentType) {
+                if (true == Arrays.equals(mTypeParam, contentType)) {
+                    return THE_FIRST_PART;
+                }
+            }
+        }
+
+        return THE_LAST_PART;
+    }
+
+    /**
+     * Check mandatory headers of a pdu.
+     *
+     * @param headers pdu headers
+     * @return true if the pdu has all of the mandatory headers, false otherwise.
+     */
+    protected static boolean checkMandatoryHeader(PduHeaders headers) {
+        if (null == headers) {
+            return false;
+        }
+
+        /* get message type */
+        int messageType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+
+        /* check Mms-Version field */
+        int mmsVersion = headers.getOctet(PduHeaders.MMS_VERSION);
+        if (0 == mmsVersion) {
+            // Every message should have Mms-Version field.
+            return false;
+        }
+
+        /* check mandatory header fields */
+        switch (messageType) {
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                // Content-Type field.
+                byte[] srContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == srContentType) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue srFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == srFrom) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] srTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == srTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+                // Response-Status field.
+                int scResponseStatus = headers.getOctet(PduHeaders.RESPONSE_STATUS);
+                if (0 == scResponseStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] scTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == scTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                // Content-Location field.
+                byte[] niContentLocation = headers.getTextString(PduHeaders.CONTENT_LOCATION);
+                if (null == niContentLocation) {
+                    return false;
+                }
+
+                // Expiry field.
+                long niExpiry = headers.getLongInteger(PduHeaders.EXPIRY);
+                if (-1 == niExpiry) {
+                    return false;
+                }
+
+                // Message-Class field.
+                byte[] niMessageClass = headers.getTextString(PduHeaders.MESSAGE_CLASS);
+                if (null == niMessageClass) {
+                    return false;
+                }
+
+                // Message-Size field.
+                long niMessageSize = headers.getLongInteger(PduHeaders.MESSAGE_SIZE);
+                if (-1 == niMessageSize) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] niTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == niTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                // Status field.
+                int nriStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == nriStatus) {
+                    return false;
+                }
+
+                // Transaction-Id field.
+                byte[] nriTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == nriTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                // Content-Type field.
+                byte[] rcContentType = headers.getTextString(PduHeaders.CONTENT_TYPE);
+                if (null == rcContentType) {
+                    return false;
+                }
+
+                // Date field.
+                long rcDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == rcDate) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                // Date field.
+                long diDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == diDate) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] diMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == diMessageId) {
+                    return false;
+                }
+
+                // Status field.
+                int diStatus = headers.getOctet(PduHeaders.STATUS);
+                if (0 == diStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] diTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == diTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                // Transaction-Id field.
+                byte[] aiTransactionId = headers.getTextString(PduHeaders.TRANSACTION_ID);
+                if (null == aiTransactionId) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                // Date field.
+                long roDate = headers.getLongInteger(PduHeaders.DATE);
+                if (-1 == roDate) {
+                    return false;
+                }
+
+                // From field.
+                EncodedStringValue roFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == roFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] roMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == roMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int roReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == roReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] roTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == roTo) {
+                    return false;
+                }
+
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                // From field.
+                EncodedStringValue rrFrom = headers.getEncodedStringValue(PduHeaders.FROM);
+                if (null == rrFrom) {
+                    return false;
+                }
+
+                // Message-Id field.
+                byte[] rrMessageId = headers.getTextString(PduHeaders.MESSAGE_ID);
+                if (null == rrMessageId) {
+                    return false;
+                }
+
+                // Read-Status field.
+                int rrReadStatus = headers.getOctet(PduHeaders.READ_STATUS);
+                if (0 == rrReadStatus) {
+                    return false;
+                }
+
+                // To field.
+                EncodedStringValue[] rrTo = headers.getEncodedStringValues(PduHeaders.TO);
+                if (null == rrTo) {
+                    return false;
+                }
+
+                break;
+            default:
+                // Parser doesn't support this message type in this version.
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/PduPart.java b/telephony/java/com/google/android/mms/pdu/PduPart.java
new file mode 100644
index 0000000..09b7751
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPart.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.net.Uri;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The pdu part.
+ */
+public class PduPart {
+    /**
+     * Well-Known Parameters.
+     */
+    public static final int P_Q                  = 0x80;
+    public static final int P_CHARSET            = 0x81;
+    public static final int P_LEVEL              = 0x82;
+    public static final int P_TYPE               = 0x83;
+    public static final int P_DEP_NAME           = 0x85;
+    public static final int P_DEP_FILENAME       = 0x86;
+    public static final int P_DIFFERENCES        = 0x87;
+    public static final int P_PADDING            = 0x88;
+    // This value of "TYPE" s used with Content-Type: multipart/related
+    public static final int P_CT_MR_TYPE         = 0x89;
+    public static final int P_DEP_START          = 0x8A;
+    public static final int P_DEP_START_INFO     = 0x8B;
+    public static final int P_DEP_COMMENT        = 0x8C;
+    public static final int P_DEP_DOMAIN         = 0x8D;
+    public static final int P_MAX_AGE            = 0x8E;
+    public static final int P_DEP_PATH           = 0x8F;
+    public static final int P_SECURE             = 0x90;
+    public static final int P_SEC                = 0x91;
+    public static final int P_MAC                = 0x92;
+    public static final int P_CREATION_DATE      = 0x93;
+    public static final int P_MODIFICATION_DATE  = 0x94;
+    public static final int P_READ_DATE          = 0x95;
+    public static final int P_SIZE               = 0x96;
+    public static final int P_NAME               = 0x97;
+    public static final int P_FILENAME           = 0x98;
+    public static final int P_START              = 0x99;
+    public static final int P_START_INFO         = 0x9A;
+    public static final int P_COMMENT            = 0x9B;
+    public static final int P_DOMAIN             = 0x9C;
+    public static final int P_PATH               = 0x9D;
+
+    /**
+     *  Header field names.
+     */
+     public static final int P_CONTENT_TYPE       = 0x91;
+     public static final int P_CONTENT_LOCATION   = 0x8E;
+     public static final int P_CONTENT_ID         = 0xC0;
+     public static final int P_DEP_CONTENT_DISPOSITION = 0xAE;
+     public static final int P_CONTENT_DISPOSITION = 0xC5;
+    // The next header is unassigned header, use reserved header(0x48) value.
+     public static final int P_CONTENT_TRANSFER_ENCODING = 0xC8;
+
+     /**
+      * Content=Transfer-Encoding string.
+      */
+     public static final String CONTENT_TRANSFER_ENCODING =
+             "Content-Transfer-Encoding";
+
+     /**
+      * Value of Content-Transfer-Encoding.
+      */
+     public static final String P_BINARY = "binary";
+     public static final String P_7BIT = "7bit";
+     public static final String P_8BIT = "8bit";
+     public static final String P_BASE64 = "base64";
+     public static final String P_QUOTED_PRINTABLE = "quoted-printable";
+
+     /**
+      * Value of disposition can be set to PduPart when the value is octet in
+      * the PDU.
+      * "from-data" instead of Form-data<Octet 128>.
+      * "attachment" instead of Attachment<Octet 129>.
+      * "inline" instead of Inline<Octet 130>.
+      */
+     static final byte[] DISPOSITION_FROM_DATA = "from-data".getBytes();
+     static final byte[] DISPOSITION_ATTACHMENT = "attachment".getBytes();
+     static final byte[] DISPOSITION_INLINE = "inline".getBytes();
+
+     /**
+      * Content-Disposition value.
+      */
+     public static final int P_DISPOSITION_FROM_DATA  = 0x80;
+     public static final int P_DISPOSITION_ATTACHMENT = 0x81;
+     public static final int P_DISPOSITION_INLINE     = 0x82;
+
+     /**
+      * Header of part.
+      */
+     private Map<Integer, Object> mPartHeader = null;
+
+     /**
+      * Data uri.
+      */
+     private Uri mUri = null;
+
+     /**
+      * Part data.
+      */
+     private byte[] mPartData = null;
+
+     private static final String TAG = "PduPart";
+
+     /**
+      * Empty Constructor.
+      */
+     @UnsupportedAppUsage
+     public PduPart() {
+         mPartHeader = new HashMap<Integer, Object>();
+     }
+
+     /**
+      * Set part data. The data are stored as byte array.
+      *
+      * @param data the data
+      */
+     @UnsupportedAppUsage
+     public void setData(byte[] data) {
+         if(data == null) {
+            return;
+        }
+
+         mPartData = new byte[data.length];
+         System.arraycopy(data, 0, mPartData, 0, data.length);
+     }
+
+     /**
+      * @return A copy of the part data or null if the data wasn't set or
+      *         the data is stored as Uri.
+      * @see #getDataUri
+      */
+     @UnsupportedAppUsage
+     public byte[] getData() {
+         if(mPartData == null) {
+            return null;
+         }
+
+         byte[] byteArray = new byte[mPartData.length];
+         System.arraycopy(mPartData, 0, byteArray, 0, mPartData.length);
+         return byteArray;
+     }
+
+    /**
+     * @return The length of the data, if this object have data, else 0.
+     */
+     @UnsupportedAppUsage
+     public int getDataLength() {
+         if(mPartData != null){
+             return mPartData.length;
+         } else {
+             return 0;
+         }
+     }
+
+
+     /**
+      * Set data uri. The data are stored as Uri.
+      *
+      * @param uri the uri
+      */
+     @UnsupportedAppUsage
+     public void setDataUri(Uri uri) {
+         mUri = uri;
+     }
+
+     /**
+      * @return The Uri of the part data or null if the data wasn't set or
+      *         the data is stored as byte array.
+      * @see #getData
+      */
+     @UnsupportedAppUsage
+     public Uri getDataUri() {
+         return mUri;
+     }
+
+     /**
+      * Set Content-id value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentId(byte[] contentId) {
+         if((contentId == null) || (contentId.length == 0)) {
+             throw new IllegalArgumentException(
+                     "Content-Id may not be null or empty.");
+         }
+
+         if ((contentId.length > 1)
+                 && ((char) contentId[0] == '<')
+                 && ((char) contentId[contentId.length - 1] == '>')) {
+             mPartHeader.put(P_CONTENT_ID, contentId);
+             return;
+         }
+
+         // Insert beginning '<' and trailing '>' for Content-Id.
+         byte[] buffer = new byte[contentId.length + 2];
+         buffer[0] = (byte) (0xff & '<');
+         buffer[buffer.length - 1] = (byte) (0xff & '>');
+         System.arraycopy(contentId, 0, buffer, 1, contentId.length);
+         mPartHeader.put(P_CONTENT_ID, buffer);
+     }
+
+     /**
+      * Get Content-id value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentId() {
+         return (byte[]) mPartHeader.get(P_CONTENT_ID);
+     }
+
+     /**
+      * Set Char-set value.
+      *
+      * @param charset the value
+      */
+     @UnsupportedAppUsage
+     public void setCharset(int charset) {
+         mPartHeader.put(P_CHARSET, charset);
+     }
+
+     /**
+      * Get Char-set value
+      *
+      * @return the charset value. Return 0 if charset was not set.
+      */
+     @UnsupportedAppUsage
+     public int getCharset() {
+         Integer charset = (Integer) mPartHeader.get(P_CHARSET);
+         if(charset == null) {
+             return 0;
+         } else {
+             return charset.intValue();
+         }
+     }
+
+     /**
+      * Set Content-Location value.
+      *
+      * @param contentLocation the value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentLocation(byte[] contentLocation) {
+         if(contentLocation == null) {
+             throw new NullPointerException("null content-location");
+         }
+
+         mPartHeader.put(P_CONTENT_LOCATION, contentLocation);
+     }
+
+     /**
+      * Get Content-Location value.
+      *
+      * @return the value
+      *     return PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      *     return PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      *     return PduPart.disposition[2] instead of <Octet 130> (Inline).
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentLocation() {
+         return (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+     }
+
+     /**
+      * Set Content-Disposition value.
+      * Use PduPart.disposition[0] instead of <Octet 128> (Form-data).
+      * Use PduPart.disposition[1] instead of <Octet 129> (Attachment).
+      * Use PduPart.disposition[2] instead of <Octet 130> (Inline).
+      *
+      * @param contentDisposition the value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentDisposition(byte[] contentDisposition) {
+         if(contentDisposition == null) {
+             throw new NullPointerException("null content-disposition");
+         }
+
+         mPartHeader.put(P_CONTENT_DISPOSITION, contentDisposition);
+     }
+
+     /**
+      * Get Content-Disposition value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentDisposition() {
+         return (byte[]) mPartHeader.get(P_CONTENT_DISPOSITION);
+     }
+
+     /**
+      *  Set Content-Type value.
+      *
+      *  @param value the value
+      *  @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentType(byte[] contentType) {
+         if(contentType == null) {
+             throw new NullPointerException("null content-type");
+         }
+
+         mPartHeader.put(P_CONTENT_TYPE, contentType);
+     }
+
+     /**
+      * Get Content-Type value of part.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentType() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TYPE);
+     }
+
+     /**
+      * Set Content-Transfer-Encoding value
+      *
+      * @param contentId the content-id value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setContentTransferEncoding(byte[] contentTransferEncoding) {
+         if(contentTransferEncoding == null) {
+             throw new NullPointerException("null content-transfer-encoding");
+         }
+
+         mPartHeader.put(P_CONTENT_TRANSFER_ENCODING, contentTransferEncoding);
+     }
+
+     /**
+      * Get Content-Transfer-Encoding value.
+      *
+      * @return the value
+      */
+     @UnsupportedAppUsage
+     public byte[] getContentTransferEncoding() {
+         return (byte[]) mPartHeader.get(P_CONTENT_TRANSFER_ENCODING);
+     }
+
+     /**
+      * Set Content-type parameter: name.
+      *
+      * @param name the name value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setName(byte[] name) {
+         if(null == name) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_NAME, name);
+     }
+
+     /**
+      *  Get content-type parameter: name.
+      *
+      *  @return the name
+      */
+     @UnsupportedAppUsage
+     public byte[] getName() {
+         return (byte[]) mPartHeader.get(P_NAME);
+     }
+
+     /**
+      * Get Content-disposition parameter: filename
+      *
+      * @param fileName the filename value
+      * @throws NullPointerException if the value is null.
+      */
+     @UnsupportedAppUsage
+     public void setFilename(byte[] fileName) {
+         if(null == fileName) {
+             throw new NullPointerException("null content-id");
+         }
+
+         mPartHeader.put(P_FILENAME, fileName);
+     }
+
+     /**
+      * Set Content-disposition parameter: filename
+      *
+      * @return the filename
+      */
+     @UnsupportedAppUsage
+     public byte[] getFilename() {
+         return (byte[]) mPartHeader.get(P_FILENAME);
+     }
+
+    @UnsupportedAppUsage
+    public String generateLocation() {
+        // Assumption: At least one of the content-location / name / filename
+        // or content-id should be set. This is guaranteed by the PduParser
+        // for incoming messages and by MM composer for outgoing messages.
+        byte[] location = (byte[]) mPartHeader.get(P_NAME);
+        if(null == location) {
+            location = (byte[]) mPartHeader.get(P_FILENAME);
+
+            if (null == location) {
+                location = (byte[]) mPartHeader.get(P_CONTENT_LOCATION);
+            }
+        }
+
+        if (null == location) {
+            byte[] contentId = (byte[]) mPartHeader.get(P_CONTENT_ID);
+            return "cid:" + new String(contentId);
+        } else {
+            return new String(location);
+        }
+    }
+}
+
diff --git a/telephony/java/com/google/android/mms/pdu/PduPersister.java b/telephony/java/com/google/android/mms/pdu/PduPersister.java
new file mode 100755
index 0000000..93f3065
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/PduPersister.java
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.drm.DrmManagerClient;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.Telephony;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.InvalidHeaderValueException;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.util.DownloadDrmHelper;
+import com.google.android.mms.util.DrmConvertSession;
+import com.google.android.mms.util.PduCache;
+import com.google.android.mms.util.PduCacheEntry;
+import com.google.android.mms.util.SqliteWrapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class is the high-level manager of PDU storage.
+ */
+public class PduPersister {
+    private static final String TAG = "PduPersister";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final long DUMMY_THREAD_ID = Long.MAX_VALUE;
+
+    /**
+     * The uri of temporary drm objects.
+     */
+    public static final String TEMPORARY_DRM_OBJECT_URI =
+        "content://mms/" + Long.MAX_VALUE + "/part";
+    /**
+     * Indicate that we transiently failed to process a MM.
+     */
+    public static final int PROC_STATUS_TRANSIENT_FAILURE   = 1;
+    /**
+     * Indicate that we permanently failed to process a MM.
+     */
+    public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
+    /**
+     * Indicate that we have successfully processed a MM.
+     */
+    public static final int PROC_STATUS_COMPLETED           = 3;
+
+    private static PduPersister sPersister;
+    @UnsupportedAppUsage
+    private static final PduCache PDU_CACHE_INSTANCE;
+
+    @UnsupportedAppUsage
+    private static final int[] ADDRESS_FIELDS = new int[] {
+            PduHeaders.BCC,
+            PduHeaders.CC,
+            PduHeaders.FROM,
+            PduHeaders.TO
+    };
+
+    private static final String[] PDU_PROJECTION = new String[] {
+        Mms._ID,
+        Mms.MESSAGE_BOX,
+        Mms.THREAD_ID,
+        Mms.RETRIEVE_TEXT,
+        Mms.SUBJECT,
+        Mms.CONTENT_LOCATION,
+        Mms.CONTENT_TYPE,
+        Mms.MESSAGE_CLASS,
+        Mms.MESSAGE_ID,
+        Mms.RESPONSE_TEXT,
+        Mms.TRANSACTION_ID,
+        Mms.CONTENT_CLASS,
+        Mms.DELIVERY_REPORT,
+        Mms.MESSAGE_TYPE,
+        Mms.MMS_VERSION,
+        Mms.PRIORITY,
+        Mms.READ_REPORT,
+        Mms.READ_STATUS,
+        Mms.REPORT_ALLOWED,
+        Mms.RETRIEVE_STATUS,
+        Mms.STATUS,
+        Mms.DATE,
+        Mms.DELIVERY_TIME,
+        Mms.EXPIRY,
+        Mms.MESSAGE_SIZE,
+        Mms.SUBJECT_CHARSET,
+        Mms.RETRIEVE_TEXT_CHARSET,
+    };
+
+    private static final int PDU_COLUMN_ID                    = 0;
+    private static final int PDU_COLUMN_MESSAGE_BOX           = 1;
+    private static final int PDU_COLUMN_THREAD_ID             = 2;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
+    private static final int PDU_COLUMN_SUBJECT               = 4;
+    private static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
+    private static final int PDU_COLUMN_CONTENT_TYPE          = 6;
+    private static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
+    private static final int PDU_COLUMN_MESSAGE_ID            = 8;
+    private static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
+    private static final int PDU_COLUMN_TRANSACTION_ID        = 10;
+    private static final int PDU_COLUMN_CONTENT_CLASS         = 11;
+    private static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
+    private static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
+    private static final int PDU_COLUMN_MMS_VERSION           = 14;
+    private static final int PDU_COLUMN_PRIORITY              = 15;
+    private static final int PDU_COLUMN_READ_REPORT           = 16;
+    private static final int PDU_COLUMN_READ_STATUS           = 17;
+    private static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
+    private static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
+    private static final int PDU_COLUMN_STATUS                = 20;
+    private static final int PDU_COLUMN_DATE                  = 21;
+    private static final int PDU_COLUMN_DELIVERY_TIME         = 22;
+    private static final int PDU_COLUMN_EXPIRY                = 23;
+    private static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
+    private static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
+    private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
+
+    @UnsupportedAppUsage
+    private static final String[] PART_PROJECTION = new String[] {
+        Part._ID,
+        Part.CHARSET,
+        Part.CONTENT_DISPOSITION,
+        Part.CONTENT_ID,
+        Part.CONTENT_LOCATION,
+        Part.CONTENT_TYPE,
+        Part.FILENAME,
+        Part.NAME,
+        Part.TEXT
+    };
+
+    private static final int PART_COLUMN_ID                  = 0;
+    private static final int PART_COLUMN_CHARSET             = 1;
+    private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
+    private static final int PART_COLUMN_CONTENT_ID          = 3;
+    private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
+    private static final int PART_COLUMN_CONTENT_TYPE        = 5;
+    private static final int PART_COLUMN_FILENAME            = 6;
+    private static final int PART_COLUMN_NAME                = 7;
+    private static final int PART_COLUMN_TEXT                = 8;
+
+    @UnsupportedAppUsage
+    private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP;
+    // These map are used for convenience in persist() and load().
+    private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP;
+    private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP;
+    @UnsupportedAppUsage
+    private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP;
+
+    static {
+        MESSAGE_BOX_MAP = new HashMap<Uri, Integer>();
+        MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
+        MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
+        MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
+        MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
+
+        CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
+        CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
+
+        CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
+        CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
+
+        // Encoded string field code -> column index/name map.
+        ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
+
+        ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
+        ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
+
+        // Text string field code -> column index/name map.
+        TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
+
+        TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
+        TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
+
+        // Octet field code -> column index/name map.
+        OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
+        OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
+
+        OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
+        OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
+
+        // Long field code -> column index/name map.
+        LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
+        LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
+
+        LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
+        LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
+
+        PDU_CACHE_INSTANCE = PduCache.getInstance();
+     }
+
+    @UnsupportedAppUsage
+    private final Context mContext;
+    @UnsupportedAppUsage
+    private final ContentResolver mContentResolver;
+    private final DrmManagerClient mDrmManagerClient;
+    @UnsupportedAppUsage
+    private final TelephonyManager mTelephonyManager;
+
+    private PduPersister(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        mDrmManagerClient = new DrmManagerClient(context);
+        mTelephonyManager = (TelephonyManager)context
+                .getSystemService(Context.TELEPHONY_SERVICE);
+     }
+
+    /** Get(or create if not exist) an instance of PduPersister */
+    @UnsupportedAppUsage
+    public static PduPersister getPduPersister(Context context) {
+        if ((sPersister == null)) {
+            sPersister = new PduPersister(context);
+        } else if (!context.equals(sPersister.mContext)) {
+            sPersister.release();
+            sPersister = new PduPersister(context);
+        }
+
+        return sPersister;
+    }
+
+    private void setEncodedStringValueToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if ((s != null) && (s.length() > 0)) {
+            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
+            int charset = c.getInt(charsetColumnIndex);
+            EncodedStringValue value = new EncodedStringValue(
+                    charset, getBytes(s));
+            headers.setEncodedStringValue(value, mapColumn);
+        }
+    }
+
+    private void setTextStringToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        String s = c.getString(columnIndex);
+        if (s != null) {
+            headers.setTextString(getBytes(s), mapColumn);
+        }
+    }
+
+    private void setOctetToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) throws InvalidHeaderValueException {
+        if (!c.isNull(columnIndex)) {
+            int b = c.getInt(columnIndex);
+            headers.setOctet(b, mapColumn);
+        }
+    }
+
+    private void setLongToHeaders(
+            Cursor c, int columnIndex,
+            PduHeaders headers, int mapColumn) {
+        if (!c.isNull(columnIndex)) {
+            long l = c.getLong(columnIndex);
+            headers.setLongInteger(l, mapColumn);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return c.getInt(columnIndex);
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) {
+        if (!c.isNull(columnIndex)) {
+            return getBytes(c.getString(columnIndex));
+        }
+        return null;
+    }
+
+    private PduPart[] loadParts(long msgId) throws MmsException {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/part"),
+                PART_PROJECTION, null, null, null);
+
+        PduPart[] parts = null;
+
+        try {
+            if ((c == null) || (c.getCount() == 0)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
+                }
+                return null;
+            }
+
+            int partCount = c.getCount();
+            int partIdx = 0;
+            parts = new PduPart[partCount];
+            while (c.moveToNext()) {
+                PduPart part = new PduPart();
+                Integer charset = getIntegerFromPartColumn(
+                        c, PART_COLUMN_CHARSET);
+                if (charset != null) {
+                    part.setCharset(charset);
+                }
+
+                byte[] contentDisposition = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_DISPOSITION);
+                if (contentDisposition != null) {
+                    part.setContentDisposition(contentDisposition);
+                }
+
+                byte[] contentId = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_ID);
+                if (contentId != null) {
+                    part.setContentId(contentId);
+                }
+
+                byte[] contentLocation = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_LOCATION);
+                if (contentLocation != null) {
+                    part.setContentLocation(contentLocation);
+                }
+
+                byte[] contentType = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_CONTENT_TYPE);
+                if (contentType != null) {
+                    part.setContentType(contentType);
+                } else {
+                    throw new MmsException("Content-Type must be set.");
+                }
+
+                byte[] fileName = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_FILENAME);
+                if (fileName != null) {
+                    part.setFilename(fileName);
+                }
+
+                byte[] name = getByteArrayFromPartColumn(
+                        c, PART_COLUMN_NAME);
+                if (name != null) {
+                    part.setName(name);
+                }
+
+                // Construct a Uri for this part.
+                long partId = c.getLong(PART_COLUMN_ID);
+                Uri partURI = Uri.parse("content://mms/part/" + partId);
+                part.setDataUri(partURI);
+
+                // For images/audio/video, we won't keep their data in Part
+                // because their renderer accept Uri as source.
+                String type = toIsoString(contentType);
+                if (!ContentType.isImageType(type)
+                        && !ContentType.isAudioType(type)
+                        && !ContentType.isVideoType(type)) {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    InputStream is = null;
+
+                    // Store simple string values directly in the database instead of an
+                    // external file.  This makes the text searchable and retrieval slightly
+                    // faster.
+                    if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
+                            || ContentType.TEXT_HTML.equals(type)) {
+                        String text = c.getString(PART_COLUMN_TEXT);
+                        byte [] blob = new EncodedStringValue(text != null ? text : "")
+                            .getTextString();
+                        baos.write(blob, 0, blob.length);
+                    } else {
+
+                        try {
+                            is = mContentResolver.openInputStream(partURI);
+
+                            byte[] buffer = new byte[256];
+                            int len = is.read(buffer);
+                            while (len >= 0) {
+                                baos.write(buffer, 0, len);
+                                len = is.read(buffer);
+                            }
+                        } catch (IOException e) {
+                            Log.e(TAG, "Failed to load part data", e);
+                            c.close();
+                            throw new MmsException(e);
+                        } finally {
+                            if (is != null) {
+                                try {
+                                    is.close();
+                                } catch (IOException e) {
+                                    Log.e(TAG, "Failed to close stream", e);
+                                } // Ignore
+                            }
+                        }
+                    }
+                    part.setData(baos.toByteArray());
+                }
+                parts[partIdx++] = part;
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        return parts;
+    }
+
+    private void loadAddress(long msgId, PduHeaders headers) {
+        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
+                null, null, null);
+
+        if (c != null) {
+            try {
+                while (c.moveToNext()) {
+                    String addr = c.getString(0);
+                    if (!TextUtils.isEmpty(addr)) {
+                        int addrType = c.getInt(2);
+                        switch (addrType) {
+                            case PduHeaders.FROM:
+                                headers.setEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            case PduHeaders.TO:
+                            case PduHeaders.CC:
+                            case PduHeaders.BCC:
+                                headers.appendEncodedStringValue(
+                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
+                                        addrType);
+                                break;
+                            default:
+                                Log.e(TAG, "Unknown address type: " + addrType);
+                                break;
+                        }
+                    }
+                }
+            } finally {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Load a PDU from storage by given Uri.
+     *
+     * @param uri The Uri of the PDU to be loaded.
+     * @return A generic PDU object, it may be cast to dedicated PDU.
+     * @throws MmsException Failed to load some fields of a PDU.
+     */
+    @UnsupportedAppUsage
+    public GenericPdu load(Uri uri) throws MmsException {
+        GenericPdu pdu = null;
+        PduCacheEntry cacheEntry = null;
+        int msgBox = 0;
+        long threadId = -1;
+        try {
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "load: " + uri + " blocked by isUpdating()");
+                    }
+                    try {
+                        PDU_CACHE_INSTANCE.wait();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "load: ", e);
+                    }
+                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+                    if (cacheEntry != null) {
+                        return cacheEntry.getPdu();
+                    }
+                }
+                // Tell the cache to indicate to other callers that this item
+                // is currently being updated.
+                PDU_CACHE_INSTANCE.setUpdating(uri, true);
+            }
+
+            Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
+                    PDU_PROJECTION, null, null, null);
+            PduHeaders headers = new PduHeaders();
+            Set<Entry<Integer, Integer>> set;
+            long msgId = ContentUris.parseId(uri);
+
+            try {
+                if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
+                    throw new MmsException("Bad uri: " + uri);
+                }
+
+                msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
+                threadId = c.getLong(PDU_COLUMN_THREAD_ID);
+
+                set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setEncodedStringValueToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setTextStringToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = OCTET_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setOctetToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+
+                set = LONG_COLUMN_INDEX_MAP.entrySet();
+                for (Entry<Integer, Integer> e : set) {
+                    setLongToHeaders(
+                            c, e.getValue(), headers, e.getKey());
+                }
+            } finally {
+                if (c != null) {
+                    c.close();
+                }
+            }
+
+            // Check whether 'msgId' has been assigned a valid value.
+            if (msgId == -1L) {
+                throw new MmsException("Error! ID of the message: -1.");
+            }
+
+            // Load address information of the MM.
+            loadAddress(msgId, headers);
+
+            int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
+            PduBody body = new PduBody();
+
+            // For PDU which type is M_retrieve.conf or Send.req, we should
+            // load multiparts and put them into the body of the PDU.
+            if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                    || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+                PduPart[] parts = loadParts(msgId);
+                if (parts != null) {
+                    int partsNum = parts.length;
+                    for (int i = 0; i < partsNum; i++) {
+                        body.addPart(parts[i]);
+                    }
+                }
+            }
+
+            switch (msgType) {
+            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                pdu = new NotificationInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
+                pdu = new DeliveryInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
+                pdu = new ReadOrigInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                pdu = new RetrieveConf(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                pdu = new SendReq(headers, body);
+                break;
+            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
+                pdu = new AcknowledgeInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
+                pdu = new NotifyRespInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
+                pdu = new ReadRecInd(headers);
+                break;
+            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
+            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
+            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
+            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
+            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
+                throw new MmsException(
+                        "Unsupported PDU type: " + Integer.toHexString(msgType));
+
+            default:
+                throw new MmsException(
+                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
+            }
+        } finally {
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (pdu != null) {
+                    assert(PDU_CACHE_INSTANCE.get(uri) == null);
+                    // Update the cache entry with the real info
+                    cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
+                    PDU_CACHE_INSTANCE.put(uri, cacheEntry);
+                }
+                PDU_CACHE_INSTANCE.setUpdating(uri, false);
+                PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
+            }
+        }
+        return pdu;
+    }
+
+    @UnsupportedAppUsage
+    private void persistAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        ContentValues values = new ContentValues(3);
+
+        for (EncodedStringValue addr : array) {
+            values.clear(); // Clear all values first.
+            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
+            values.put(Addr.CHARSET, addr.getCharacterSet());
+            values.put(Addr.TYPE, type);
+
+            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
+            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        }
+    }
+
+    @UnsupportedAppUsage
+    private static String getPartContentType(PduPart part) {
+        return part.getContentType() == null ? null : toIsoString(part.getContentType());
+    }
+
+    @UnsupportedAppUsage
+    public Uri persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
+        ContentValues values = new ContentValues(8);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = getPartContentType(part);
+        if (contentType != null) {
+            // There is no "image/jpg" in Android (and it's an invalid mimetype).
+            // Change it to "image/jpeg"
+            if (ContentType.IMAGE_JPG.equals(contentType)) {
+                contentType = ContentType.IMAGE_JPEG;
+            }
+
+            values.put(Part.CONTENT_TYPE, contentType);
+            // To ensure the SMIL part is always the first part.
+            if (ContentType.APP_SMIL.equals(contentType)) {
+                values.put(Part.SEQ, -1);
+            }
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+        if (res == null) {
+            throw new MmsException("Failed to persist part, return null.");
+        }
+
+        persistData(part, res, contentType, preOpenedFiles);
+        // After successfully store the data, we should update
+        // the dataUri of the part.
+        part.setDataUri(res);
+
+        return res;
+    }
+
+    /**
+     * Save data of the part into storage. The source data may be given
+     * by a byte[] or a Uri. If it's a byte[], directly save it
+     * into storage, otherwise load source data from the dataUri and then
+     * save it. If the data is an image, we may scale down it according
+     * to user preference.
+     *
+     * @param part The PDU part which contains data to be saved.
+     * @param uri The URI of the part.
+     * @param contentType The MIME type of the part.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @throws MmsException Cannot find source data or error occurred
+     *         while saving the data.
+     */
+    private void persistData(PduPart part, Uri uri,
+            String contentType, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        OutputStream os = null;
+        InputStream is = null;
+        DrmConvertSession drmConvertSession = null;
+        Uri dataUri = null;
+        String path = null;
+
+        try {
+            byte[] data = part.getData();
+            if (ContentType.TEXT_PLAIN.equals(contentType)
+                    || ContentType.APP_SMIL.equals(contentType)
+                    || ContentType.TEXT_HTML.equals(contentType)) {
+                ContentValues cv = new ContentValues();
+                if (data == null) {
+                    data = new String("").getBytes(CharacterSets.DEFAULT_CHARSET_NAME);
+                }
+                cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString());
+                if (mContentResolver.update(uri, cv, null, null) != 1) {
+                    throw new MmsException("unable to update " + uri.toString());
+                }
+            } else {
+                boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
+                if (isDrm) {
+                    if (uri != null) {
+                        try (ParcelFileDescriptor pfd =
+                                mContentResolver.openFileDescriptor(uri, "r")) {
+                            if (pfd.getStatSize() > 0) {
+                                // we're not going to re-persist and re-encrypt an already
+                                // converted drm file
+                                return;
+                            }
+                        } catch (Exception e) {
+                            Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
+                        }
+                    }
+                    // We haven't converted the file yet, start the conversion
+                    drmConvertSession = DrmConvertSession.open(mContext, contentType);
+                    if (drmConvertSession == null) {
+                        throw new MmsException("Mimetype " + contentType +
+                                " can not be converted.");
+                    }
+                }
+                // uri can look like:
+                // content://mms/part/98
+                os = mContentResolver.openOutputStream(uri);
+                if (data == null) {
+                    dataUri = part.getDataUri();
+                    if ((dataUri == null) || (dataUri.equals(uri))) {
+                        Log.w(TAG, "Can't find data for this part.");
+                        return;
+                    }
+                    // dataUri can look like:
+                    // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715586
+                    if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
+                        is = preOpenedFiles.get(dataUri);
+                    }
+                    if (is == null) {
+                        is = mContentResolver.openInputStream(dataUri);
+                    }
+
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+
+                    byte[] buffer = new byte[8192];
+                    for (int len = 0; (len = is.read(buffer)) != -1; ) {
+                        if (!isDrm) {
+                            os.write(buffer, 0, len);
+                        } else {
+                            byte[] convertedData = drmConvertSession.convert(buffer, len);
+                            if (convertedData != null) {
+                                os.write(convertedData, 0, convertedData.length);
+                            } else {
+                                throw new MmsException("Error converting drm data.");
+                            }
+                        }
+                    }
+                } else {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Saving data to: " + uri);
+                    }
+                    if (!isDrm) {
+                        os.write(data);
+                    } else {
+                        dataUri = uri;
+                        byte[] convertedData = drmConvertSession.convert(data, data.length);
+                        if (convertedData != null) {
+                            os.write(convertedData, 0, convertedData.length);
+                        } else {
+                            throw new MmsException("Error converting drm data.");
+                        }
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Failed to open Input/Output stream.", e);
+            throw new MmsException(e);
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to read/write data.", e);
+            throw new MmsException(e);
+        } finally {
+            if (os != null) {
+                try {
+                    os.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + os, e);
+                } // Ignore
+            }
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    Log.e(TAG, "IOException while closing: " + is, e);
+                } // Ignore
+            }
+            if (drmConvertSession != null) {
+                drmConvertSession.close(path);
+
+                // Reset the permissions on the encrypted part file so everyone has only read
+                // permission.
+                File f = new File(path);
+                ContentValues values = new ContentValues(0);
+                SqliteWrapper.update(mContext, mContentResolver,
+                                     Uri.parse("content://mms/resetFilePerm/" + f.getName()),
+                                     values, null, null);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    private void updateAddress(
+            long msgId, int type, EncodedStringValue[] array) {
+        // Delete old address information and then insert new ones.
+        SqliteWrapper.delete(mContext, mContentResolver,
+                Uri.parse("content://mms/" + msgId + "/addr"),
+                Addr.TYPE + "=" + type, null);
+
+        persistAddress(msgId, type, array);
+    }
+
+    /**
+     * Update headers of a SendReq.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param pdu New headers.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    @UnsupportedAppUsage
+    public void updateHeaders(Uri uri, SendReq sendReq) {
+        synchronized(PDU_CACHE_INSTANCE) {
+            // If the cache item is getting updated, wait until it's done updating before
+            // purging it.
+            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
+                }
+                try {
+                    PDU_CACHE_INSTANCE.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "updateHeaders: ", e);
+                }
+            }
+        }
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        ContentValues values = new ContentValues(10);
+        byte[] contentType = sendReq.getContentType();
+        if (contentType != null) {
+            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
+        }
+
+        long date = sendReq.getDate();
+        if (date != -1) {
+            values.put(Mms.DATE, date);
+        }
+
+        int deliveryReport = sendReq.getDeliveryReport();
+        if (deliveryReport != 0) {
+            values.put(Mms.DELIVERY_REPORT, deliveryReport);
+        }
+
+        long expiry = sendReq.getExpiry();
+        if (expiry != -1) {
+            values.put(Mms.EXPIRY, expiry);
+        }
+
+        byte[] msgClass = sendReq.getMessageClass();
+        if (msgClass != null) {
+            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
+        }
+
+        int priority = sendReq.getPriority();
+        if (priority != 0) {
+            values.put(Mms.PRIORITY, priority);
+        }
+
+        int readReport = sendReq.getReadReport();
+        if (readReport != 0) {
+            values.put(Mms.READ_REPORT, readReport);
+        }
+
+        byte[] transId = sendReq.getTransactionId();
+        if (transId != null) {
+            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
+        }
+
+        EncodedStringValue subject = sendReq.getSubject();
+        if (subject != null) {
+            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
+            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
+        } else {
+            values.put(Mms.SUBJECT, "");
+        }
+
+        long messageSize = sendReq.getMessageSize();
+        if (messageSize > 0) {
+            values.put(Mms.MESSAGE_SIZE, messageSize);
+        }
+
+        PduHeaders headers = sendReq.getPduHeaders();
+        HashSet<String> recipients = new HashSet<String>();
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = headers.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = headers.getEncodedStringValues(addrType);
+            }
+
+            if (array != null) {
+                long msgId = ContentUris.parseId(uri);
+                updateAddress(msgId, addrType, array);
+                if (addrType == PduHeaders.TO) {
+                    for (EncodedStringValue v : array) {
+                        if (v != null) {
+                            recipients.add(v.getString());
+                        }
+                    }
+                }
+            }
+        }
+        if (!recipients.isEmpty()) {
+            long threadId = Threads.getOrCreateThreadId(mContext, recipients);
+            values.put(Mms.THREAD_ID, threadId);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+    }
+
+    private void updatePart(Uri uri, PduPart part, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        ContentValues values = new ContentValues(7);
+
+        int charset = part.getCharset();
+        if (charset != 0 ) {
+            values.put(Part.CHARSET, charset);
+        }
+
+        String contentType = null;
+        if (part.getContentType() != null) {
+            contentType = toIsoString(part.getContentType());
+            values.put(Part.CONTENT_TYPE, contentType);
+        } else {
+            throw new MmsException("MIME type of the part must be set.");
+        }
+
+        if (part.getFilename() != null) {
+            String fileName = new String(part.getFilename());
+            values.put(Part.FILENAME, fileName);
+        }
+
+        if (part.getName() != null) {
+            String name = new String(part.getName());
+            values.put(Part.NAME, name);
+        }
+
+        Object value = null;
+        if (part.getContentDisposition() != null) {
+            value = toIsoString(part.getContentDisposition());
+            values.put(Part.CONTENT_DISPOSITION, (String) value);
+        }
+
+        if (part.getContentId() != null) {
+            value = toIsoString(part.getContentId());
+            values.put(Part.CONTENT_ID, (String) value);
+        }
+
+        if (part.getContentLocation() != null) {
+            value = toIsoString(part.getContentLocation());
+            values.put(Part.CONTENT_LOCATION, (String) value);
+        }
+
+        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
+
+        // Only update the data when:
+        // 1. New binary data supplied or
+        // 2. The Uri of the part is different from the current one.
+        if ((part.getData() != null)
+                || (!uri.equals(part.getDataUri()))) {
+            persistData(part, uri, contentType, preOpenedFiles);
+        }
+    }
+
+    /**
+     * Update all parts of a PDU.
+     *
+     * @param uri The PDU which need to be updated.
+     * @param body New message body of the PDU.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @throws MmsException Bad URI or updating failed.
+     */
+    @UnsupportedAppUsage
+    public void updateParts(Uri uri, PduBody body, HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        try {
+            PduCacheEntry cacheEntry;
+            synchronized(PDU_CACHE_INSTANCE) {
+                if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
+                    }
+                    try {
+                        PDU_CACHE_INSTANCE.wait();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "updateParts: ", e);
+                    }
+                    cacheEntry = PDU_CACHE_INSTANCE.get(uri);
+                    if (cacheEntry != null) {
+                        ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
+                    }
+                }
+                // Tell the cache to indicate to other callers that this item
+                // is currently being updated.
+                PDU_CACHE_INSTANCE.setUpdating(uri, true);
+            }
+
+            ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
+            HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();
+
+            int partsNum = body.getPartsNum();
+            StringBuilder filter = new StringBuilder().append('(');
+            for (int i = 0; i < partsNum; i++) {
+                PduPart part = body.getPart(i);
+                Uri partUri = part.getDataUri();
+                if ((partUri == null) || TextUtils.isEmpty(partUri.getAuthority())
+                        || !partUri.getAuthority().startsWith("mms")) {
+                    toBeCreated.add(part);
+                } else {
+                    toBeUpdated.put(partUri, part);
+
+                    // Don't use 'i > 0' to determine whether we should append
+                    // 'AND' since 'i = 0' may be skipped in another branch.
+                    if (filter.length() > 1) {
+                        filter.append(" AND ");
+                    }
+
+                    filter.append(Part._ID);
+                    filter.append("!=");
+                    DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
+                }
+            }
+            filter.append(')');
+
+            long msgId = ContentUris.parseId(uri);
+
+            // Remove the parts which doesn't exist anymore.
+            SqliteWrapper.delete(mContext, mContentResolver,
+                    Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
+                    filter.length() > 2 ? filter.toString() : null, null);
+
+            // Create new parts which didn't exist before.
+            for (PduPart part : toBeCreated) {
+                persistPart(part, msgId, preOpenedFiles);
+            }
+
+            // Update the modified parts.
+            for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
+                updatePart(e.getKey(), e.getValue(), preOpenedFiles);
+            }
+        } finally {
+            synchronized(PDU_CACHE_INSTANCE) {
+                PDU_CACHE_INSTANCE.setUpdating(uri, false);
+                PDU_CACHE_INSTANCE.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Persist a PDU object to specific location in the storage.
+     *
+     * @param pdu The PDU object to be stored.
+     * @param uri Where to store the given PDU object.
+     * @param createThreadId if true, this function may create a thread id for the recipients
+     * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used
+     *  to create the associated thread. When false, only the sender will be used in finding or
+     *  creating the appropriate thread or conversation.
+     * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
+     * @return A Uri which can be used to access the stored PDU.
+     */
+
+    @UnsupportedAppUsage
+    public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled,
+            HashMap<Uri, InputStream> preOpenedFiles)
+            throws MmsException {
+        if (uri == null) {
+            throw new MmsException("Uri may not be null.");
+        }
+        long msgId = -1;
+        try {
+            msgId = ContentUris.parseId(uri);
+        } catch (NumberFormatException e) {
+            // the uri ends with "inbox" or something else like that
+        }
+        boolean existingUri = msgId != -1;
+
+        if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+        synchronized(PDU_CACHE_INSTANCE) {
+            // If the cache item is getting updated, wait until it's done updating before
+            // purging it.
+            if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, "persist: " + uri + " blocked by isUpdating()");
+                }
+                try {
+                    PDU_CACHE_INSTANCE.wait();
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "persist1: ", e);
+                }
+            }
+        }
+        PDU_CACHE_INSTANCE.purge(uri);
+
+        PduHeaders header = pdu.getPduHeaders();
+        PduBody body = null;
+        ContentValues values = new ContentValues();
+        Set<Entry<Integer, String>> set;
+
+        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set) {
+            int field = e.getKey();
+            EncodedStringValue encodedString = header.getEncodedStringValue(field);
+            if (encodedString != null) {
+                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
+                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
+                values.put(charsetColumn, encodedString.getCharacterSet());
+            }
+        }
+
+        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            byte[] text = header.getTextString(e.getKey());
+            if (text != null) {
+                values.put(e.getValue(), toIsoString(text));
+            }
+        }
+
+        set = OCTET_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            int b = header.getOctet(e.getKey());
+            if (b != 0) {
+                values.put(e.getValue(), b);
+            }
+        }
+
+        set = LONG_COLUMN_NAME_MAP.entrySet();
+        for (Entry<Integer, String> e : set){
+            long l = header.getLongInteger(e.getKey());
+            if (l != -1L) {
+                values.put(e.getValue(), l);
+            }
+        }
+
+        HashMap<Integer, EncodedStringValue[]> addressMap =
+                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = null;
+            if (addrType == PduHeaders.FROM) {
+                EncodedStringValue v = header.getEncodedStringValue(addrType);
+                if (v != null) {
+                    array = new EncodedStringValue[1];
+                    array[0] = v;
+                }
+            } else {
+                array = header.getEncodedStringValues(addrType);
+            }
+            addressMap.put(addrType, array);
+        }
+
+        HashSet<String> recipients = new HashSet<String>();
+        int msgType = pdu.getMessageType();
+        // Here we only allocate thread ID for M-Notification.ind,
+        // M-Retrieve.conf and M-Send.req.
+        // Some of other PDU types may be allocated a thread ID outside
+        // this scope.
+        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
+                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
+                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
+            switch (msgType) {
+                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
+                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
+                    loadRecipients(PduHeaders.FROM, recipients, addressMap, false);
+
+                    // For received messages when group MMS is enabled, we want to associate this
+                    // message with the thread composed of all the recipients -- all but our own
+                    // number, that is. This includes the person who sent the
+                    // message or the FROM field (above) in addition to the other people the message
+                    // was addressed to or the TO field. Our own number is in that TO field and
+                    // we have to ignore it in loadRecipients.
+                    if (groupMmsEnabled) {
+                        loadRecipients(PduHeaders.TO, recipients, addressMap, true);
+
+                        // Also load any numbers in the CC field to address group messaging
+                        // compatibility issues with devices that place numbers in this field
+                        // for group messages.
+                        loadRecipients(PduHeaders.CC, recipients, addressMap, true);
+                    }
+                    break;
+                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
+                    loadRecipients(PduHeaders.TO, recipients, addressMap, false);
+                    break;
+            }
+            long threadId = 0;
+            if (createThreadId && !recipients.isEmpty()) {
+                // Given all the recipients associated with this message, find (or create) the
+                // correct thread.
+                threadId = Threads.getOrCreateThreadId(mContext, recipients);
+            }
+            values.put(Mms.THREAD_ID, threadId);
+        }
+
+        // Save parts first to avoid inconsistent message is loaded
+        // while saving the parts.
+        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
+
+        // Figure out if this PDU is a text-only message
+        boolean textOnly = true;
+
+        // Sum up the total message size
+        int messageSize = 0;
+
+        // Get body if the PDU is a RetrieveConf or SendReq.
+        if (pdu instanceof MultimediaMessagePdu) {
+            body = ((MultimediaMessagePdu) pdu).getBody();
+            // Start saving parts if necessary.
+            if (body != null) {
+                int partsNum = body.getPartsNum();
+                if (partsNum > 2) {
+                    // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
+                    // Down a few lines below we're checking to make sure we've only got SMIL or
+                    // text. We also have to check then we don't have more than two parts.
+                    // Otherwise, a slideshow with two text slides would be marked as textOnly.
+                    textOnly = false;
+                }
+                for (int i = 0; i < partsNum; i++) {
+                    PduPart part = body.getPart(i);
+                    messageSize += part.getDataLength();
+                    persistPart(part, dummyId, preOpenedFiles);
+
+                    // If we've got anything besides text/plain or SMIL part, then we've got
+                    // an mms message with some other type of attachment.
+                    String contentType = getPartContentType(part);
+                    if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
+                            && !ContentType.TEXT_PLAIN.equals(contentType)) {
+                        textOnly = false;
+                    }
+                }
+            }
+        }
+        // Record whether this mms message is a simple plain text or not. This is a hint for the
+        // UI.
+        values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
+        // The message-size might already have been inserted when parsing the
+        // PDU header. If not, then we insert the message size as well.
+        if (values.getAsInteger(Mms.MESSAGE_SIZE) == null) {
+            values.put(Mms.MESSAGE_SIZE, messageSize);
+        }
+
+        Uri res = null;
+        if (existingUri) {
+            res = uri;
+            SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
+        } else {
+            res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
+            if (res == null) {
+                throw new MmsException("persist() failed: return null.");
+            }
+            // Get the real ID of the PDU and update all parts which were
+            // saved with the dummy ID.
+            msgId = ContentUris.parseId(res);
+        }
+
+        values = new ContentValues(1);
+        values.put(Part.MSG_ID, msgId);
+        SqliteWrapper.update(mContext, mContentResolver,
+                             Uri.parse("content://mms/" + dummyId + "/part"),
+                             values, null, null);
+        // We should return the longest URI of the persisted PDU, for
+        // example, if input URI is "content://mms/inbox" and the _ID of
+        // persisted PDU is '8', we should return "content://mms/inbox/8"
+        // instead of "content://mms/8".
+        // FIXME: Should the MmsProvider be responsible for this???
+        if (!existingUri) {
+            res = Uri.parse(uri + "/" + msgId);
+        }
+
+        // Save address information.
+        for (int addrType : ADDRESS_FIELDS) {
+            EncodedStringValue[] array = addressMap.get(addrType);
+            if (array != null) {
+                persistAddress(msgId, addrType, array);
+            }
+        }
+
+        return res;
+    }
+
+    /**
+     * For a given address type, extract the recipients from the headers.
+     *
+     * @param addressType can be PduHeaders.FROM, PduHeaders.TO or PduHeaders.CC
+     * @param recipients a HashSet that is loaded with the recipients from the FROM, TO or CC headers
+     * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header
+     * @param excludeMyNumber if true, the number of this phone will be excluded from recipients
+     */
+    @UnsupportedAppUsage
+    private void loadRecipients(int addressType, HashSet<String> recipients,
+            HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) {
+        EncodedStringValue[] array = addressMap.get(addressType);
+        if (array == null) {
+            return;
+        }
+        // If the TO recipients is only a single address, then we can skip loadRecipients when
+        // we're excluding our own number because we know that address is our own.
+        if (excludeMyNumber && array.length == 1) {
+            return;
+        }
+        final SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext);
+        final Set<String> myPhoneNumbers = new HashSet<String>();
+        if (excludeMyNumber) {
+            // Build a list of my phone numbers from the various sims.
+            for (int subid : subscriptionManager.getActiveSubscriptionIdList()) {
+                final String myNumber = mTelephonyManager.getLine1Number(subid);
+                if (myNumber != null) {
+                    myPhoneNumbers.add(myNumber);
+                }
+            }
+        }
+
+        for (EncodedStringValue v : array) {
+            if (v != null) {
+                final String number = v.getString();
+                if (excludeMyNumber) {
+                    for (final String myNumber : myPhoneNumbers) {
+                        if (!PhoneNumberUtils.compare(number, myNumber)
+                                && !recipients.contains(number)) {
+                            // Only add numbers which aren't my own number.
+                            recipients.add(number);
+                            break;
+                        }
+                    }
+                } else if (!recipients.contains(number)){
+                    recipients.add(number);
+                }
+            }
+        }
+    }
+
+    /**
+     * Move a PDU object from one location to another.
+     *
+     * @param from Specify the PDU object to be moved.
+     * @param to The destination location, should be one of the following:
+     *        "content://mms/inbox", "content://mms/sent",
+     *        "content://mms/drafts", "content://mms/outbox",
+     *        "content://mms/trash".
+     * @return New Uri of the moved PDU.
+     * @throws MmsException Error occurred while moving the message.
+     */
+    @UnsupportedAppUsage
+    public Uri move(Uri from, Uri to) throws MmsException {
+        // Check whether the 'msgId' has been assigned a valid value.
+        long msgId = ContentUris.parseId(from);
+        if (msgId == -1L) {
+            throw new MmsException("Error! ID of the message: -1.");
+        }
+
+        // Get corresponding int value of destination box.
+        Integer msgBox = MESSAGE_BOX_MAP.get(to);
+        if (msgBox == null) {
+            throw new MmsException(
+                    "Bad destination, must be one of "
+                    + "content://mms/inbox, content://mms/sent, "
+                    + "content://mms/drafts, content://mms/outbox, "
+                    + "content://mms/temp.");
+        }
+
+        ContentValues values = new ContentValues(1);
+        values.put(Mms.MESSAGE_BOX, msgBox);
+        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
+        return ContentUris.withAppendedId(to, msgId);
+    }
+
+    /**
+     * Wrap a byte[] into a String.
+     */
+    @UnsupportedAppUsage
+    public static String toIsoString(byte[] bytes) {
+        try {
+            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return "";
+        }
+    }
+
+    /**
+     * Unpack a given String into a byte[].
+     */
+    @UnsupportedAppUsage
+    public static byte[] getBytes(String data) {
+        try {
+            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
+        } catch (UnsupportedEncodingException e) {
+            // Impossible to reach here!
+            Log.e(TAG, "ISO_8859_1 must be supported!", e);
+            return new byte[0];
+        }
+    }
+
+    /**
+     * Remove all objects in the temporary path.
+     */
+    public void release() {
+        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
+        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
+    }
+
+    /**
+     * Find all messages to be sent or downloaded before certain time.
+     */
+    @UnsupportedAppUsage
+    public Cursor getPendingMessages(long dueTime) {
+        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
+        uriBuilder.appendQueryParameter("protocol", "mms");
+
+        String selection = PendingMessages.ERROR_TYPE + " < ?"
+                + " AND " + PendingMessages.DUE_TIME + " <= ?";
+
+        String[] selectionArgs = new String[] {
+                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
+                String.valueOf(dueTime)
+        };
+
+        return SqliteWrapper.query(mContext, mContentResolver,
+                uriBuilder.build(), null, selection, selectionArgs,
+                PendingMessages.DUE_TIME);
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
new file mode 100644
index 0000000..9d6535c
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/QuotedPrintable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.ByteArrayOutputStream;
+
+public class QuotedPrintable {
+    private static byte ESCAPE_CHAR = '=';
+
+    /**
+     * Decodes an array quoted-printable characters into an array of original bytes.
+     * Escaped characters are converted back to their original representation.
+     *
+     * <p>
+     * This function implements a subset of
+     * quoted-printable encoding specification (rule #1 and rule #2)
+     * as defined in RFC 1521.
+     * </p>
+     *
+     * @param bytes array of quoted-printable characters
+     * @return array of original bytes,
+     *         null if quoted-printable decoding is unsuccessful.
+     */
+    @UnsupportedAppUsage
+    public static final byte[] decodeQuotedPrintable(byte[] bytes) {
+        if (bytes == null) {
+            return null;
+        }
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        for (int i = 0; i < bytes.length; i++) {
+            int b = bytes[i];
+            if (b == ESCAPE_CHAR) {
+                try {
+                    if('\r' == (char)bytes[i + 1] &&
+                            '\n' == (char)bytes[i + 2]) {
+                        i += 2;
+                        continue;
+                    }
+                    int u = Character.digit((char) bytes[++i], 16);
+                    int l = Character.digit((char) bytes[++i], 16);
+                    if (u == -1 || l == -1) {
+                        return null;
+                    }
+                    buffer.write((char) ((u << 4) + l));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    return null;
+                }
+            } else {
+                buffer.write(b);
+            }
+        }
+        return buffer.toByteArray();
+    }
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
new file mode 100644
index 0000000..e38c62d
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadOrigInd.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadOrigInd extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    public ReadOrigInd() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_ORIG_IND);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    ReadOrigInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/ReadRecInd.java b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
new file mode 100644
index 0000000..9696bc2
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/ReadRecInd.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class ReadRecInd extends GenericPdu {
+    /**
+     * Constructor, used when composing a M-ReadRec.ind pdu.
+     *
+     * @param from the from value
+     * @param messageId the message ID value
+     * @param mmsVersion current viersion of mms
+     * @param readStatus the read status value
+     * @param to the to value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if messageId or to is null.
+     */
+    @UnsupportedAppUsage
+    public ReadRecInd(EncodedStringValue from,
+                      byte[] messageId,
+                      int mmsVersion,
+                      int readStatus,
+                      EncodedStringValue[] to) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_READ_REC_IND);
+        setFrom(from);
+        setMessageId(messageId);
+        setMmsVersion(mmsVersion);
+        setTo(to);
+        setReadStatus(readStatus);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    ReadRecInd(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Date value.
+     *
+     * @return the value
+     */
+    public long getDate() {
+        return mPduHeaders.getLongInteger(PduHeaders.DATE);
+    }
+
+    /**
+     * Set Date value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setDate(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.DATE);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get To value.
+     *
+     * @return the value
+     */
+    public EncodedStringValue[] getTo() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.TO);
+    }
+
+    /**
+     * Set To value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-MMS-Read-status value.
+     *
+     * @return the value
+     */
+    public int getReadStatus() {
+        return mPduHeaders.getOctet(PduHeaders.READ_STATUS);
+    }
+
+    /**
+     * Set X-MMS-Read-status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setReadStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_STATUS);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/RetrieveConf.java b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
new file mode 100644
index 0000000..03755af
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/RetrieveConf.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+/**
+ * M-Retrieve.conf Pdu.
+ */
+public class RetrieveConf extends MultimediaMessagePdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    @UnsupportedAppUsage
+    public RetrieveConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    RetrieveConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    @UnsupportedAppUsage
+    RetrieveConf(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get From value.
+     * From-value = Value-length
+     *      (Address-present-token Encoded-string-value | Insert-address-token)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getFrom() {
+       return mPduHeaders.getEncodedStringValue(PduHeaders.FROM);
+    }
+
+    /**
+     * Set From value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setFrom(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.FROM);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Status value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getRetrieveStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Status value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setRetrieveStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RETRIEVE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Retrieve-Text value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue getRetrieveText() {
+        return mPduHeaders.getEncodedStringValue(PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Set X-Mms-Retrieve-Text value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setRetrieveText(EncodedStringValue value) {
+        mPduHeaders.setEncodedStringValue(value, PduHeaders.RETRIEVE_TEXT);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public byte getDistributionIndicator() {return 0x00;}
+     *     public void setDistributionIndicator(byte value) {}
+     *
+     *     public PreviouslySentByValue getPreviouslySentBy() {return null;}
+     *     public void setPreviouslySentBy(PreviouslySentByValue value) {}
+     *
+     *     public PreviouslySentDateValue getPreviouslySentDate() {}
+     *     public void setPreviouslySentDate(PreviouslySentDateValue value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplaceId() {return 0x00;}
+     *     public void setReplaceId(byte[] value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendConf.java b/telephony/java/com/google/android/mms/pdu/SendConf.java
new file mode 100644
index 0000000..b859827
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendConf.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2007 Esmertec AG.
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendConf extends GenericPdu {
+    /**
+     * Empty constructor.
+     * Since the Pdu corresponding to this class is constructed
+     * by the Proxy-Relay server, this class is only instantiated
+     * by the Pdu Parser.
+     *
+     * @throws InvalidHeaderValueException if error occurs.
+     */
+    @UnsupportedAppUsage
+    public SendConf() throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_CONF);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    @UnsupportedAppUsage
+    SendConf(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Get Message-ID value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageId() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Set Message-ID value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setMessageId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_ID);
+    }
+
+    /**
+     * Get X-Mms-Response-Status.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getResponseStatus() {
+        return mPduHeaders.getOctet(PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Set X-Mms-Response-Status.
+     *
+     * @param value the values
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    public void setResponseStatus(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.RESPONSE_STATUS);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    public void setTransactionId(byte[] value) {
+            mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *    public byte[] getContentLocation() {return null;}
+     *    public void setContentLocation(byte[] value) {}
+     *
+     *    public EncodedStringValue getResponseText() {return null;}
+     *    public void setResponseText(EncodedStringValue value) {}
+     *
+     *    public byte getStoreStatus() {return 0x00;}
+     *    public void setStoreStatus(byte value) {}
+     *
+     *    public byte[] getStoreStatusText() {return null;}
+     *    public void setStoreStatusText(byte[] value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/SendReq.java b/telephony/java/com/google/android/mms/pdu/SendReq.java
new file mode 100644
index 0000000..c1b7f93
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/SendReq.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007-2008 Esmertec AG.
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.pdu;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.InvalidHeaderValueException;
+
+public class SendReq extends MultimediaMessagePdu {
+    private static final String TAG = "SendReq";
+
+    @UnsupportedAppUsage
+    public SendReq() {
+        super();
+
+        try {
+            setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+            setMmsVersion(PduHeaders.CURRENT_MMS_VERSION);
+            // FIXME: Content-type must be decided according to whether
+            // SMIL part present.
+            setContentType("application/vnd.wap.multipart.related".getBytes());
+            setFrom(new EncodedStringValue(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR.getBytes()));
+            setTransactionId(generateTransactionId());
+        } catch (InvalidHeaderValueException e) {
+            // Impossible to reach here since all headers we set above are valid.
+            Log.e(TAG, "Unexpected InvalidHeaderValueException.", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private byte[] generateTransactionId() {
+        String transactionId = "T" + Long.toHexString(System.currentTimeMillis());
+        return transactionId.getBytes();
+    }
+
+    /**
+     * Constructor, used when composing a M-Send.req pdu.
+     *
+     * @param contentType the content type value
+     * @param from the from value
+     * @param mmsVersion current viersion of mms
+     * @param transactionId the transaction-id value
+     * @throws InvalidHeaderValueException if parameters are invalid.
+     *         NullPointerException if contentType, form or transactionId is null.
+     */
+    public SendReq(byte[] contentType,
+                   EncodedStringValue from,
+                   int mmsVersion,
+                   byte[] transactionId) throws InvalidHeaderValueException {
+        super();
+        setMessageType(PduHeaders.MESSAGE_TYPE_SEND_REQ);
+        setContentType(contentType);
+        setFrom(from);
+        setMmsVersion(mmsVersion);
+        setTransactionId(transactionId);
+    }
+
+    /**
+     * Constructor with given headers.
+     *
+     * @param headers Headers for this PDU.
+     */
+    SendReq(PduHeaders headers) {
+        super(headers);
+    }
+
+    /**
+     * Constructor with given headers and body
+     *
+     * @param headers Headers for this PDU.
+     * @param body Body of this PDu.
+     */
+    @UnsupportedAppUsage
+    SendReq(PduHeaders headers, PduBody body) {
+        super(headers, body);
+    }
+
+    /**
+     * Get Bcc value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getBcc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.BCC);
+    }
+
+    /**
+     * Add a "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addBcc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Set "BCC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setBcc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.BCC);
+    }
+
+    /**
+     * Get CC value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public EncodedStringValue[] getCc() {
+        return mPduHeaders.getEncodedStringValues(PduHeaders.CC);
+    }
+
+    /**
+     * Add a "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void addCc(EncodedStringValue value) {
+        mPduHeaders.appendEncodedStringValue(value, PduHeaders.CC);
+    }
+
+    /**
+     * Set "CC" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setCc(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.CC);
+    }
+
+    /**
+     * Get Content-type value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getContentType() {
+        return mPduHeaders.getTextString(PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Set Content-type value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setContentType(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.CONTENT_TYPE);
+    }
+
+    /**
+     * Get X-Mms-Delivery-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getDeliveryReport() {
+        return mPduHeaders.getOctet(PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Delivery-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setDeliveryReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.DELIVERY_REPORT);
+    }
+
+    /**
+     * Get X-Mms-Expiry value.
+     *
+     * Expiry-value = Value-length
+     *      (Absolute-token Date-value | Relative-token Delta-seconds-value)
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getExpiry() {
+        return mPduHeaders.getLongInteger(PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Set X-Mms-Expiry value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setExpiry(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.EXPIRY);
+    }
+
+    /**
+     * Get X-Mms-MessageSize value.
+     *
+     * Expiry-value = size of message
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public long getMessageSize() {
+        return mPduHeaders.getLongInteger(PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Set X-Mms-MessageSize value.
+     *
+     * @param value the value
+     */
+    @UnsupportedAppUsage
+    public void setMessageSize(long value) {
+        mPduHeaders.setLongInteger(value, PduHeaders.MESSAGE_SIZE);
+    }
+
+    /**
+     * Get X-Mms-Message-Class value.
+     * Message-class-value = Class-identifier | Token-text
+     * Class-identifier = Personal | Advertisement | Informational | Auto
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public byte[] getMessageClass() {
+        return mPduHeaders.getTextString(PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Set X-Mms-Message-Class value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setMessageClass(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.MESSAGE_CLASS);
+    }
+
+    /**
+     * Get X-Mms-Read-Report value.
+     *
+     * @return the value
+     */
+    @UnsupportedAppUsage
+    public int getReadReport() {
+        return mPduHeaders.getOctet(PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set X-Mms-Read-Report value.
+     *
+     * @param value the value
+     * @throws InvalidHeaderValueException if the value is invalid.
+     */
+    @UnsupportedAppUsage
+    public void setReadReport(int value) throws InvalidHeaderValueException {
+        mPduHeaders.setOctet(value, PduHeaders.READ_REPORT);
+    }
+
+    /**
+     * Set "To" value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTo(EncodedStringValue[] value) {
+        mPduHeaders.setEncodedStringValues(value, PduHeaders.TO);
+    }
+
+    /**
+     * Get X-Mms-Transaction-Id field value.
+     *
+     * @return the X-Mms-Report-Allowed value
+     */
+    @UnsupportedAppUsage
+    public byte[] getTransactionId() {
+        return mPduHeaders.getTextString(PduHeaders.TRANSACTION_ID);
+    }
+
+    /**
+     * Set X-Mms-Transaction-Id field value.
+     *
+     * @param value the value
+     * @throws NullPointerException if the value is null.
+     */
+    @UnsupportedAppUsage
+    public void setTransactionId(byte[] value) {
+        mPduHeaders.setTextString(value, PduHeaders.TRANSACTION_ID);
+    }
+
+    /*
+     * Optional, not supported header fields:
+     *
+     *     public byte getAdaptationAllowed() {return 0};
+     *     public void setAdaptationAllowed(btye value) {};
+     *
+     *     public byte[] getApplicId() {return null;}
+     *     public void setApplicId(byte[] value) {}
+     *
+     *     public byte[] getAuxApplicId() {return null;}
+     *     public void getAuxApplicId(byte[] value) {}
+     *
+     *     public byte getContentClass() {return 0x00;}
+     *     public void setApplicId(byte value) {}
+     *
+     *     public long getDeliveryTime() {return 0};
+     *     public void setDeliveryTime(long value) {};
+     *
+     *     public byte getDrmContent() {return 0x00;}
+     *     public void setDrmContent(byte value) {}
+     *
+     *     public MmFlagsValue getMmFlags() {return null;}
+     *     public void setMmFlags(MmFlagsValue value) {}
+     *
+     *     public MmStateValue getMmState() {return null;}
+     *     public void getMmState(MmStateValue value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getReplyCharging() {return 0x00;}
+     *     public void setReplyCharging(byte value) {}
+     *
+     *     public byte getReplyChargingDeadline() {return 0x00;}
+     *     public void setReplyChargingDeadline(byte value) {}
+     *
+     *     public byte[] getReplyChargingId() {return 0x00;}
+     *     public void setReplyChargingId(byte[] value) {}
+     *
+     *     public long getReplyChargingSize() {return 0;}
+     *     public void setReplyChargingSize(long value) {}
+     *
+     *     public byte[] getReplyApplicId() {return 0x00;}
+     *     public void setReplyApplicId(byte[] value) {}
+     *
+     *     public byte getStore() {return 0x00;}
+     *     public void setStore(byte value) {}
+     */
+}
diff --git a/telephony/java/com/google/android/mms/pdu/package.html b/telephony/java/com/google/android/mms/pdu/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/pdu/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/telephony/java/com/google/android/mms/util/AbstractCache.java b/telephony/java/com/google/android/mms/util/AbstractCache.java
new file mode 100644
index 0000000..ab5d48a
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/AbstractCache.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+
+public abstract class AbstractCache<K, V> {
+    private static final String TAG = "AbstractCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final int MAX_CACHED_ITEMS  = 500;
+
+    private final HashMap<K, CacheEntry<V>> mCacheMap;
+
+    @UnsupportedAppUsage
+    protected AbstractCache() {
+        mCacheMap = new HashMap<K, CacheEntry<V>>();
+    }
+
+    @UnsupportedAppUsage
+    public boolean put(K key, V value) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to put " + key + " into cache.");
+        }
+
+        if (mCacheMap.size() >= MAX_CACHED_ITEMS) {
+            // TODO Should remove the oldest or least hit cached entry
+            // and then cache the new one.
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Failed! size limitation reached.");
+            }
+            return false;
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = new CacheEntry<V>();
+            cacheEntry.value = value;
+            mCacheMap.put(key, cacheEntry);
+
+            if (LOCAL_LOGV) {
+                Log.v(TAG, key + " cached, " + mCacheMap.size() + " items total.");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage
+    public V get(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to get " + key + " from cache.");
+        }
+
+        if (key != null) {
+            CacheEntry<V> cacheEntry = mCacheMap.get(key);
+            if (cacheEntry != null) {
+                cacheEntry.hit++;
+                if (LOCAL_LOGV) {
+                    Log.v(TAG, key + " hit " + cacheEntry.hit + " times.");
+                }
+                return cacheEntry.value;
+            }
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    public V purge(K key) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Trying to purge " + key);
+        }
+
+        CacheEntry<V> v = mCacheMap.remove(key);
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, mCacheMap.size() + " items cached.");
+        }
+
+        return v != null ? v.value : null;
+    }
+
+    @UnsupportedAppUsage
+    public void purgeAll() {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purging cache, " + mCacheMap.size()
+                    + " items dropped.");
+        }
+        mCacheMap.clear();
+    }
+
+    public int size() {
+        return mCacheMap.size();
+    }
+
+    private static class CacheEntry<V> {
+        int hit;
+        V value;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
new file mode 100644
index 0000000..118de46
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DownloadDrmHelper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmManagerClient;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public class DownloadDrmHelper {
+    private static final String TAG = "DownloadDrmHelper";
+
+    /** The MIME type of special DRM files */
+    public static final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
+
+    /** The extensions of special DRM files */
+    public static final String EXTENSION_DRM_MESSAGE = ".dm";
+
+    public static final String EXTENSION_INTERNAL_FWDL = ".fl";
+
+    /**
+     * Checks if the Media Type is a DRM Media Type
+     *
+     * @param drmManagerClient A DrmManagerClient
+     * @param mimetype Media Type to check
+     * @return True if the Media Type is DRM else false
+     */
+    public static boolean isDrmMimeType(Context context, String mimetype) {
+        boolean result = false;
+        if (context != null) {
+            try {
+                DrmManagerClient drmClient = new DrmManagerClient(context);
+                if (drmClient != null && mimetype != null && mimetype.length() > 0) {
+                    result = drmClient.canHandle("", mimetype);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Checks if the Media Type needs to be DRM converted
+     *
+     * @param mimetype Media type of the content
+     * @return True if convert is needed else false
+     */
+    @UnsupportedAppUsage
+    public static boolean isDrmConvertNeeded(String mimetype) {
+        return MIMETYPE_DRM_MESSAGE.equals(mimetype);
+    }
+
+    /**
+     * Modifies the file extension for a DRM Forward Lock file NOTE: This
+     * function shouldn't be called if the file shouldn't be DRM converted
+     */
+    @UnsupportedAppUsage
+    public static String modifyDrmFwLockFileExtension(String filename) {
+        if (filename != null) {
+            int extensionIndex;
+            extensionIndex = filename.lastIndexOf(".");
+            if (extensionIndex != -1) {
+                filename = filename.substring(0, extensionIndex);
+            }
+            filename = filename.concat(EXTENSION_INTERNAL_FWDL);
+        }
+        return filename;
+    }
+
+    /**
+     * Gets the original mime type of DRM protected content.
+     *
+     * @param context The context
+     * @param path Path to the file
+     * @param containingMime The current mime type of the file i.e. the
+     *            containing mime type
+     * @return The original mime type of the file if DRM protected else the
+     *         currentMime
+     */
+    public static String getOriginalMimeType(Context context, String path, String containingMime) {
+        String result = containingMime;
+        DrmManagerClient drmClient = new DrmManagerClient(context);
+        try {
+            if (drmClient.canHandle(path, null)) {
+                result = drmClient.getOriginalMimeType(path);
+            }
+        } catch (IllegalArgumentException ex) {
+            Log.w(TAG,
+                    "Can't get original mime type since path is null or empty string.");
+        } catch (IllegalStateException ex) {
+            Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+        }
+        return result;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/DrmConvertSession.java b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
new file mode 100644
index 0000000..0e8ec91
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/DrmConvertSession.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package com.google.android.mms.util;
+
+import android.content.Context;
+import android.drm.DrmConvertedStatus;
+import android.drm.DrmManagerClient;
+import android.provider.Downloads;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+
+public class DrmConvertSession {
+    private DrmManagerClient mDrmClient;
+    private int mConvertSessionId;
+    private static final String TAG = "DrmConvertSession";
+
+    private DrmConvertSession(DrmManagerClient drmClient, int convertSessionId) {
+        mDrmClient = drmClient;
+        mConvertSessionId = convertSessionId;
+    }
+
+    /**
+     * Start of converting a file.
+     *
+     * @param context The context of the application running the convert session.
+     * @param mimeType Mimetype of content that shall be converted.
+     * @return A convert session or null in case an error occurs.
+     */
+    @UnsupportedAppUsage
+    public static DrmConvertSession open(Context context, String mimeType) {
+        DrmManagerClient drmClient = null;
+        int convertSessionId = -1;
+        if (context != null && mimeType != null && !mimeType.equals("")) {
+            try {
+                drmClient = new DrmManagerClient(context);
+                try {
+                    convertSessionId = drmClient.openConvertSession(mimeType);
+                } catch (IllegalArgumentException e) {
+                    Log.w(TAG, "Conversion of Mimetype: " + mimeType
+                            + " is not supported.", e);
+                } catch (IllegalStateException e) {
+                    Log.w(TAG, "Could not access Open DrmFramework.", e);
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG,
+                        "DrmManagerClient instance could not be created, context is Illegal.");
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "DrmManagerClient didn't initialize properly.");
+            }
+        }
+
+        if (drmClient == null || convertSessionId < 0) {
+            return null;
+        } else {
+            return new DrmConvertSession(drmClient, convertSessionId);
+        }
+    }
+    /**
+     * Convert a buffer of data to protected format.
+     *
+     * @param buffer Buffer filled with data to convert.
+     * @param size The number of bytes that shall be converted.
+     * @return A Buffer filled with converted data, if execution is ok, in all
+     *         other case null.
+     */
+    @UnsupportedAppUsage
+    public byte [] convert(byte[] inBuffer, int size) {
+        byte[] result = null;
+        if (inBuffer != null) {
+            DrmConvertedStatus convertedStatus = null;
+            try {
+                if (size != inBuffer.length) {
+                    byte[] buf = new byte[size];
+                    System.arraycopy(inBuffer, 0, buf, 0, size);
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, buf);
+                } else {
+                    convertedStatus = mDrmClient.convertData(mConvertSessionId, inBuffer);
+                }
+
+                if (convertedStatus != null &&
+                        convertedStatus.statusCode == DrmConvertedStatus.STATUS_OK &&
+                        convertedStatus.convertedData != null) {
+                    result = convertedStatus.convertedData;
+                }
+            } catch (IllegalArgumentException e) {
+                Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
+                        + mConvertSessionId, e);
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not convert data. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        } else {
+            throw new IllegalArgumentException("Parameter inBuffer is null");
+        }
+        return result;
+    }
+
+    /**
+     * Ends a conversion session of a file.
+     *
+     * @param fileName The filename of the converted file.
+     * @return Downloads.Impl.STATUS_SUCCESS if execution is ok.
+     *         Downloads.Impl.STATUS_FILE_ERROR in case converted file can not
+     *         be accessed. Downloads.Impl.STATUS_NOT_ACCEPTABLE if a problem
+     *         occurs when accessing drm framework.
+     *         Downloads.Impl.STATUS_UNKNOWN_ERROR if a general error occurred.
+     */
+    @UnsupportedAppUsage
+    public int close(String filename) {
+        DrmConvertedStatus convertedStatus = null;
+        int result = Downloads.Impl.STATUS_UNKNOWN_ERROR;
+        if (mDrmClient != null && mConvertSessionId >= 0) {
+            try {
+                convertedStatus = mDrmClient.closeConvertSession(mConvertSessionId);
+                if (convertedStatus == null ||
+                        convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
+                        convertedStatus.convertedData == null) {
+                    result = Downloads.Impl.STATUS_NOT_ACCEPTABLE;
+                } else {
+                    RandomAccessFile rndAccessFile = null;
+                    try {
+                        rndAccessFile = new RandomAccessFile(filename, "rw");
+                        rndAccessFile.seek(convertedStatus.offset);
+                        rndAccessFile.write(convertedStatus.convertedData);
+                        result = Downloads.Impl.STATUS_SUCCESS;
+                    } catch (FileNotFoundException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "File: " + filename + " could not be found.", e);
+                    } catch (IOException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not access File: " + filename + " .", e);
+                    } catch (IllegalArgumentException e) {
+                        result = Downloads.Impl.STATUS_FILE_ERROR;
+                        Log.w(TAG, "Could not open file in mode: rw", e);
+                    } catch (SecurityException e) {
+                        Log.w(TAG, "Access to File: " + filename +
+                                " was denied denied by SecurityManager.", e);
+                    } finally {
+                        if (rndAccessFile != null) {
+                            try {
+                                rndAccessFile.close();
+                            } catch (IOException e) {
+                                result = Downloads.Impl.STATUS_FILE_ERROR;
+                                Log.w(TAG, "Failed to close File:" + filename
+                                        + ".", e);
+                            }
+                        }
+                    }
+                }
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Could not close convertsession. Convertsession: " +
+                        mConvertSessionId, e);
+            }
+        }
+        return result;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCache.java b/telephony/java/com/google/android/mms/util/PduCache.java
new file mode 100644
index 0000000..94e3894
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCache.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.content.ContentUris;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.provider.Telephony.Mms;
+import android.util.Log;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public final class PduCache extends AbstractCache<Uri, PduCacheEntry> {
+    private static final String TAG = "PduCache";
+    private static final boolean DEBUG = false;
+    private static final boolean LOCAL_LOGV = false;
+
+    private static final int MMS_ALL             = 0;
+    private static final int MMS_ALL_ID          = 1;
+    private static final int MMS_INBOX           = 2;
+    private static final int MMS_INBOX_ID        = 3;
+    private static final int MMS_SENT            = 4;
+    private static final int MMS_SENT_ID         = 5;
+    private static final int MMS_DRAFTS          = 6;
+    private static final int MMS_DRAFTS_ID       = 7;
+    private static final int MMS_OUTBOX          = 8;
+    private static final int MMS_OUTBOX_ID       = 9;
+    private static final int MMS_CONVERSATION    = 10;
+    private static final int MMS_CONVERSATION_ID = 11;
+
+    private static final UriMatcher URI_MATCHER;
+    private static final HashMap<Integer, Integer> MATCH_TO_MSGBOX_ID_MAP;
+
+    private static PduCache sInstance;
+
+    static {
+        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+        URI_MATCHER.addURI("mms", null,         MMS_ALL);
+        URI_MATCHER.addURI("mms", "#",          MMS_ALL_ID);
+        URI_MATCHER.addURI("mms", "inbox",      MMS_INBOX);
+        URI_MATCHER.addURI("mms", "inbox/#",    MMS_INBOX_ID);
+        URI_MATCHER.addURI("mms", "sent",       MMS_SENT);
+        URI_MATCHER.addURI("mms", "sent/#",     MMS_SENT_ID);
+        URI_MATCHER.addURI("mms", "drafts",     MMS_DRAFTS);
+        URI_MATCHER.addURI("mms", "drafts/#",   MMS_DRAFTS_ID);
+        URI_MATCHER.addURI("mms", "outbox",     MMS_OUTBOX);
+        URI_MATCHER.addURI("mms", "outbox/#",   MMS_OUTBOX_ID);
+        URI_MATCHER.addURI("mms-sms", "conversations",   MMS_CONVERSATION);
+        URI_MATCHER.addURI("mms-sms", "conversations/#", MMS_CONVERSATION_ID);
+
+        MATCH_TO_MSGBOX_ID_MAP = new HashMap<Integer, Integer>();
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_INBOX,  Mms.MESSAGE_BOX_INBOX);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_SENT,   Mms.MESSAGE_BOX_SENT);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_DRAFTS, Mms.MESSAGE_BOX_DRAFTS);
+        MATCH_TO_MSGBOX_ID_MAP.put(MMS_OUTBOX, Mms.MESSAGE_BOX_OUTBOX);
+    }
+
+    private final HashMap<Integer, HashSet<Uri>> mMessageBoxes;
+    private final HashMap<Long, HashSet<Uri>> mThreads;
+    private final HashSet<Uri> mUpdating;
+
+    @UnsupportedAppUsage
+    private PduCache() {
+        mMessageBoxes = new HashMap<Integer, HashSet<Uri>>();
+        mThreads = new HashMap<Long, HashSet<Uri>>();
+        mUpdating = new HashSet<Uri>();
+    }
+
+    @UnsupportedAppUsage
+    synchronized public static final PduCache getInstance() {
+        if (sInstance == null) {
+            if (LOCAL_LOGV) {
+                Log.v(TAG, "Constructing new PduCache instance.");
+            }
+            sInstance = new PduCache();
+        }
+        return sInstance;
+    }
+
+    @Override
+    synchronized public boolean put(Uri uri, PduCacheEntry entry) {
+        int msgBoxId = entry.getMessageBox();
+        HashSet<Uri> msgBox = mMessageBoxes.get(msgBoxId);
+        if (msgBox == null) {
+            msgBox = new HashSet<Uri>();
+            mMessageBoxes.put(msgBoxId, msgBox);
+        }
+
+        long threadId = entry.getThreadId();
+        HashSet<Uri> thread = mThreads.get(threadId);
+        if (thread == null) {
+            thread = new HashSet<Uri>();
+            mThreads.put(threadId, thread);
+        }
+
+        Uri finalKey = normalizeKey(uri);
+        boolean result = super.put(finalKey, entry);
+        if (result) {
+            msgBox.add(finalKey);
+            thread.add(finalKey);
+        }
+        setUpdating(uri, false);
+        return result;
+    }
+
+    synchronized public void setUpdating(Uri uri, boolean updating) {
+        if (updating) {
+            mUpdating.add(uri);
+        } else {
+            mUpdating.remove(uri);
+        }
+    }
+
+    @UnsupportedAppUsage
+    synchronized public boolean isUpdating(Uri uri) {
+        return mUpdating.contains(uri);
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    synchronized public PduCacheEntry purge(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case MMS_ALL_ID:
+                return purgeSingleEntry(uri);
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                return purgeSingleEntry(Uri.withAppendedPath(Mms.CONTENT_URI, msgId));
+            // Implicit batch of purge, return null.
+            case MMS_ALL:
+            case MMS_CONVERSATION:
+                purgeAll();
+                return null;
+            case MMS_INBOX:
+            case MMS_SENT:
+            case MMS_DRAFTS:
+            case MMS_OUTBOX:
+                purgeByMessageBox(MATCH_TO_MSGBOX_ID_MAP.get(match));
+                return null;
+            case MMS_CONVERSATION_ID:
+                purgeByThreadId(ContentUris.parseId(uri));
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    private PduCacheEntry purgeSingleEntry(Uri key) {
+        mUpdating.remove(key);
+        PduCacheEntry entry = super.purge(key);
+        if (entry != null) {
+            removeFromThreads(key, entry);
+            removeFromMessageBoxes(key, entry);
+            return entry;
+        }
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    @Override
+    synchronized public void purgeAll() {
+        super.purgeAll();
+
+        mMessageBoxes.clear();
+        mThreads.clear();
+        mUpdating.clear();
+    }
+
+    /**
+     * @param uri The Uri to be normalized.
+     * @return Uri The normalized key of cached entry.
+     */
+    private Uri normalizeKey(Uri uri) {
+        int match = URI_MATCHER.match(uri);
+        Uri normalizedKey = null;
+
+        switch (match) {
+            case MMS_ALL_ID:
+                normalizedKey = uri;
+                break;
+            case MMS_INBOX_ID:
+            case MMS_SENT_ID:
+            case MMS_DRAFTS_ID:
+            case MMS_OUTBOX_ID:
+                String msgId = uri.getLastPathSegment();
+                normalizedKey = Uri.withAppendedPath(Mms.CONTENT_URI, msgId);
+                break;
+            default:
+                return null;
+        }
+
+        if (LOCAL_LOGV) {
+            Log.v(TAG, uri + " -> " + normalizedKey);
+        }
+        return normalizedKey;
+    }
+
+    private void purgeByMessageBox(Integer msgBoxId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in message box: " + msgBoxId);
+        }
+
+        if (msgBoxId != null) {
+            HashSet<Uri> msgBox = mMessageBoxes.remove(msgBoxId);
+            if (msgBox != null) {
+                for (Uri key : msgBox) {
+                    mUpdating.remove(key);
+                    PduCacheEntry entry = super.purge(key);
+                    if (entry != null) {
+                        removeFromThreads(key, entry);
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeFromThreads(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> thread = mThreads.get(entry.getThreadId());
+        if (thread != null) {
+            thread.remove(key);
+        }
+    }
+
+    private void purgeByThreadId(long threadId) {
+        if (LOCAL_LOGV) {
+            Log.v(TAG, "Purge cache in thread: " + threadId);
+        }
+
+        HashSet<Uri> thread = mThreads.remove(threadId);
+        if (thread != null) {
+            for (Uri key : thread) {
+                mUpdating.remove(key);
+                PduCacheEntry entry = super.purge(key);
+                if (entry != null) {
+                    removeFromMessageBoxes(key, entry);
+                }
+            }
+        }
+    }
+
+    private void removeFromMessageBoxes(Uri key, PduCacheEntry entry) {
+        HashSet<Uri> msgBox = mThreads.get(Long.valueOf(entry.getMessageBox()));
+        if (msgBox != null) {
+            msgBox.remove(key);
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/PduCacheEntry.java b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
new file mode 100644
index 0000000..1ecd1bf
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/PduCacheEntry.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import com.google.android.mms.pdu.GenericPdu;
+
+public final class PduCacheEntry {
+    private final GenericPdu mPdu;
+    private final int mMessageBox;
+    private final long mThreadId;
+
+    @UnsupportedAppUsage
+    public PduCacheEntry(GenericPdu pdu, int msgBox, long threadId) {
+        mPdu = pdu;
+        mMessageBox = msgBox;
+        mThreadId = threadId;
+    }
+
+    @UnsupportedAppUsage
+    public GenericPdu getPdu() {
+        return mPdu;
+    }
+
+    @UnsupportedAppUsage
+    public int getMessageBox() {
+        return mMessageBox;
+    }
+
+    @UnsupportedAppUsage
+    public long getThreadId() {
+        return mThreadId;
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/SqliteWrapper.java b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
new file mode 100644
index 0000000..2dd1dc1
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/SqliteWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008 Esmertec AG.
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.mms.util;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.util.Log;
+import android.widget.Toast;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+public final class SqliteWrapper {
+    private static final String TAG = "SqliteWrapper";
+    private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
+                = "unable to open database file";
+
+    private SqliteWrapper() {
+        // Forbidden being instantiated.
+    }
+
+    // FIXME: It looks like outInfo.lowMemory does not work well as we expected.
+    // after run command: adb shell fillup -p 100, outInfo.lowMemory is still false.
+    private static boolean isLowMemory(Context context) {
+        if (null == context) {
+            return false;
+        }
+
+        ActivityManager am = (ActivityManager)
+                        context.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
+        am.getMemoryInfo(outInfo);
+
+        return outInfo.lowMemory;
+    }
+
+    // FIXME: need to optimize this method.
+    private static boolean isLowMemory(SQLiteException e) {
+        return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
+    }
+
+    @UnsupportedAppUsage
+    public static void checkSQLiteException(Context context, SQLiteException e) {
+        if (isLowMemory(e)) {
+            Toast.makeText(context, com.android.internal.R.string.low_memory,
+                    Toast.LENGTH_SHORT).show();
+        } else {
+            throw e;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static Cursor query(Context context, ContentResolver resolver, Uri uri,
+            String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        try {
+            return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when query: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static boolean requery(Context context, Cursor cursor) {
+        try {
+            return cursor.requery();
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when requery: ", e);
+            checkSQLiteException(context, e);
+            return false;
+        }
+    }
+    @UnsupportedAppUsage
+    public static int update(Context context, ContentResolver resolver, Uri uri,
+            ContentValues values, String where, String[] selectionArgs) {
+        try {
+            return resolver.update(uri, values, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when update: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static int delete(Context context, ContentResolver resolver, Uri uri,
+            String where, String[] selectionArgs) {
+        try {
+            return resolver.delete(uri, where, selectionArgs);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when delete: ", e);
+            checkSQLiteException(context, e);
+            return -1;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static Uri insert(Context context, ContentResolver resolver,
+            Uri uri, ContentValues values) {
+        try {
+            return resolver.insert(uri, values);
+        } catch (SQLiteException e) {
+            Log.e(TAG, "Catch a SQLiteException when insert: ", e);
+            checkSQLiteException(context, e);
+            return null;
+        }
+    }
+}
diff --git a/telephony/java/com/google/android/mms/util/package.html b/telephony/java/com/google/android/mms/util/package.html
new file mode 100755
index 0000000..c9f96a6
--- /dev/null
+++ b/telephony/java/com/google/android/mms/util/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 34ac3dc..adc9e22 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -21,7 +21,7 @@
 
     srcs: [
         "src/**/*.java",
-        ":framework-srcs",
+        ":framework-all-sources",
     ],
 
     api_packages: [
diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp
new file mode 100644
index 0000000..1b097a8
--- /dev/null
+++ b/tests/BootImageProfileTest/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "BootImageProfileTest",
+    srcs: ["src/**/*.java"],
+    libs: ["tradefed"],
+    test_suites: ["general-tests"],
+}
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
new file mode 100644
index 0000000..c132007
--- /dev/null
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for BootImageProfileTest">
+     <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
+         furthermore the changes in /data/local.prop don't actually seem to get picked up.
+    -->
+    <target_preparer
+        class="com.android.tradefed.targetprep.DeviceSetup">
+        <!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
+        <option name="force-skip-system-props" value="true" />
+
+        <option name="run-command" value="setprop dalvik.vm.profilesystemserver true" />
+        <option name="run-command" value="setprop dalvik.vm.profilebootclasspath true" />
+
+        <!-- Profiling does not pick up the above changes we restart the shell -->
+        <option name="run-command" value="stop" />
+        <option name="run-command" value="start" />
+
+        <!-- give it some time to restart the shell; otherwise the first unit test might fail -->
+        <option name="run-command" value="sleep 2" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.bootimageprofile.BootImageProfileTest" />
+    </test>
+</configuration>
diff --git a/tests/BootImageProfileTest/TEST_MAPPING b/tests/BootImageProfileTest/TEST_MAPPING
new file mode 100644
index 0000000..1b569f9
--- /dev/null
+++ b/tests/BootImageProfileTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "BootImageProfileTest"
+    }
+  ]
+}
diff --git a/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
new file mode 100644
index 0000000..17986a3
--- /dev/null
+++ b/tests/BootImageProfileTest/src/com/android/bootimageprofile/BootImageProfileTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bootimageprofile;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class BootImageProfileTest implements IDeviceTest {
+    private ITestDevice mTestDevice;
+    private static final String SYSTEM_SERVER_PROFILE =
+            "/data/misc/profiles/cur/0/android/primary.prof";
+
+    @Override
+    public void setDevice(ITestDevice testDevice) {
+        mTestDevice = testDevice;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mTestDevice;
+    }
+
+    /**
+     * Test that the boot image profile properties are set.
+     */
+    @Test
+    public void testProperties() throws Exception {
+        String res = mTestDevice.getProperty("dalvik.vm.profilebootclasspath");
+        assertTrue("profile boot class path not enabled", res != null && res.equals("true"));
+        res = mTestDevice.getProperty("dalvik.vm.profilesystemserver");
+        assertTrue("profile system server not enabled", res != null && res.equals("true"));
+    }
+
+    private void forceSaveProfile(String pkg) throws Exception {
+        String pid = mTestDevice.executeShellCommand("pidof " + pkg).trim();
+        assertTrue("Invalid pid " + pid, pid.length() > 0);
+        String res = mTestDevice.executeShellCommand("kill -s SIGUSR1 " + pid).trim();
+        assertTrue("kill SIGUSR1: " + res, res.length() == 0);
+    }
+
+    @Test
+    public void testSystemServerProfile() throws Exception {
+        // Trunacte the profile before force it to be saved to prevent previous profiles
+        // causing the test to pass.
+        String res;
+        res = mTestDevice.executeShellCommand("truncate -s 0 " + SYSTEM_SERVER_PROFILE).trim();
+        assertTrue(res, res.length() == 0);
+        // Force save profiles in case the system just started.
+        Thread.sleep(1000);
+        forceSaveProfile("system_server");
+        Thread.sleep(2000);
+        // Validate that the profile is non empty.
+        res = mTestDevice.executeShellCommand("profman --dump-only --profile-file="
+                + SYSTEM_SERVER_PROFILE);
+        boolean sawFramework = false;
+        boolean sawServices = false;
+        for (String line : res.split("\n")) {
+            if (line.contains("framework.jar")) {
+                sawFramework = true;
+            } else if (line.contains("services.jar")) {
+                sawServices = true;
+            }
+        }
+        assertTrue("Did not see framework.jar in " + res, sawFramework);
+        assertTrue("Did not see services.jar in " + res, sawServices);
+    }
+}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 30bb3ef..f0c5baa 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,7 +342,7 @@
 
 
 
-    // Code below generated by codegen v1.0.0.
+    // Code below generated by codegen v1.0.1.
     //
     // DO NOT MODIFY!
     //
@@ -1798,8 +1798,8 @@
     }
 
     @DataClass.Generated(
-            time = 1565126122525L,
-            codegenVersion = "1.0.0",
+            time = 1568235365376L,
+            codegenVersion = "1.0.1",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
             inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_UNDEFINED\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static  java.lang.String defaultName4()\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index 11f03a7..86f37fe 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -74,7 +74,7 @@
 
 
 
-    // Code below generated by codegen v1.0.0.
+    // Code below generated by codegen v1.0.1.
     //
     // DO NOT MODIFY!
     //
@@ -176,8 +176,8 @@
     }
 
     @DataClass.Generated(
-            time = 1565126123496L,
-            codegenVersion = "1.0.0",
+            time = 1568235366386L,
+            codegenVersion = "1.0.1",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
             inputSignatures = "  long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n  long creationTimestamp\nclass SampleWithCustomBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)\nabstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic  com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
diff --git a/tests/FlickerTests/lib/Android.bp b/tests/FlickerTests/lib/Android.bp
index 5d8ed2c..e0f0188 100644
--- a/tests/FlickerTests/lib/Android.bp
+++ b/tests/FlickerTests/lib/Android.bp
@@ -30,10 +30,23 @@
 }
 
 java_library {
+    name: "flickerlib_without_helpers",
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    exclude_srcs: ["src/**/helpers/*.java"],
+    static_libs: [
+        "cts-wm-util",
+        "platformprotosnano",
+        "layersprotosnano",
+        "truth-prebuilt"
+    ],
+}
+
+java_library {
     name: "flickerautomationhelperlib",
     sdk_version: "test_current",
     srcs: [
-        "src/com/android/server/wm/flicker/AutomationUtils.java",
+        "src/com/android/server/wm/flicker/helpers/AutomationUtils.java",
         "src/com/android/server/wm/flicker/WindowUtils.java",
     ],
     static_libs: [
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
index 84f9f871..38255ee 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/Assertions.java
@@ -24,14 +24,14 @@
  * results. Assertions are functions that are applied over a single trace entry and returns a
  * result which includes a detailed reason if the assertion fails.
  */
-class Assertions {
+public class Assertions {
     /**
      * Checks assertion on a single trace entry.
      *
      * @param <T> trace entry type to perform the assertion on.
      */
     @FunctionalInterface
-    interface TraceAssertion<T> extends Function<T, Result> {
+    public interface TraceAssertion<T> extends Function<T, Result> {
         /**
          * Returns an assertion that represents the logical negation of this assertion.
          *
@@ -46,7 +46,7 @@
      * Checks assertion on a single layers trace entry.
      */
     @FunctionalInterface
-    interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
+    public interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
 
     }
 
@@ -54,11 +54,11 @@
      * Utility class to store assertions with an identifier to help generate more useful debug
      * data when dealing with multiple assertions.
      */
-    static class NamedAssertion<T> {
-        final TraceAssertion<T> assertion;
-        final String name;
+    public static class NamedAssertion<T> {
+        public final TraceAssertion<T> assertion;
+        public final String name;
 
-        NamedAssertion(TraceAssertion<T> assertion, String name) {
+        public NamedAssertion(TraceAssertion<T> assertion, String name) {
             this.assertion = assertion;
             this.name = name;
         }
@@ -67,21 +67,21 @@
     /**
      * Contains the result of an assertion including the reason for failed assertions.
      */
-    static class Result {
-        static final String NEGATION_PREFIX = "!";
-        final boolean success;
-        final long timestamp;
-        final String assertionName;
-        final String reason;
+    public static class Result {
+        public static final String NEGATION_PREFIX = "!";
+        public final boolean success;
+        public final long timestamp;
+        public final String assertionName;
+        public final String reason;
 
-        Result(boolean success, long timestamp, String assertionName, String reason) {
+        public Result(boolean success, long timestamp, String assertionName, String reason) {
             this.success = success;
             this.timestamp = timestamp;
             this.assertionName = assertionName;
             this.reason = reason;
         }
 
-        Result(boolean success, String reason) {
+        public Result(boolean success, String reason) {
             this.success = success;
             this.reason = reason;
             this.assertionName = "";
@@ -91,7 +91,7 @@
         /**
          * Returns the negated {@code Result} and adds a negation prefix to the assertion name.
          */
-        Result negate() {
+        public Result negate() {
             String negatedAssertionName;
             if (this.assertionName.startsWith(NEGATION_PREFIX)) {
                 negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1);
@@ -101,11 +101,11 @@
             return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason);
         }
 
-        boolean passed() {
+        public boolean passed() {
             return this.success;
         }
 
-        boolean failed() {
+        public boolean failed() {
             return !this.success;
         }
 
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
index 3c65d3c..5c4df81 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AssertionsChecker.java
@@ -38,11 +38,11 @@
     private AssertionOption mOption = AssertionOption.NONE;
     private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
 
-    void add(Assertions.TraceAssertion<T> assertion, String name) {
+    public void add(Assertions.TraceAssertion<T> assertion, String name) {
         mAssertions.add(new NamedAssertion<>(assertion, name));
     }
 
-    void filterByRange(long startTime, long endTime) {
+    public void filterByRange(long startTime, long endTime) {
         mFilterEntriesByRange = true;
         mFilterStartTime = startTime;
         mFilterEndTime = endTime;
@@ -75,7 +75,7 @@
      * @param entries list of entries to perform assertions on
      * @return list of failed assertion results
      */
-    List<Result> test(List<T> entries) {
+    public List<Result> test(List<T> entries) {
         List<T> filteredEntries;
         List<Result> failures;
 
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
index 9525f41..c47f7f4 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/ITraceEntry.java
@@ -19,7 +19,7 @@
 /**
  * Common interface for Layer and WindowManager trace entries.
  */
-interface ITraceEntry {
+public interface ITraceEntry {
     /**
      * @return timestamp of current entry
      */
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
index 660ec0f..68986d4 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTrace.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.surfaceflinger.nano.Layers.LayerProto;
 import android.surfaceflinger.nano.Layers.RectProto;
@@ -25,11 +24,14 @@
 import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
 import android.util.SparseArray;
 
+import androidx.annotation.Nullable;
+
 import com.android.server.wm.flicker.Assertions.Result;
 
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -57,7 +59,7 @@
      * @param data   binary proto data
      * @param source Path to source of data for additional debug information
      */
-    static LayersTrace parseFrom(byte[] data, Path source) {
+    public static LayersTrace parseFrom(byte[] data, Path source) {
         List<Entry> entries = new ArrayList<>();
         LayersTraceFileProto fileProto;
         try {
@@ -79,15 +81,15 @@
      *
      * @param data binary proto data
      */
-    static LayersTrace parseFrom(byte[] data) {
+    public static LayersTrace parseFrom(byte[] data) {
         return parseFrom(data, null);
     }
 
-    List<Entry> getEntries() {
+    public List<Entry> getEntries() {
         return mEntries;
     }
 
-    Entry getEntry(long timestamp) {
+    public Entry getEntry(long timestamp) {
         Optional<Entry> entry = mEntries.stream()
                 .filter(e -> e.getTimestamp() == timestamp)
                 .findFirst();
@@ -97,14 +99,14 @@
         return entry.get();
     }
 
-    Optional<Path> getSource() {
+    public Optional<Path> getSource() {
         return Optional.ofNullable(mSource);
     }
 
     /**
      * Represents a single Layer trace entry.
      */
-    static class Entry implements ITraceEntry {
+    public static class Entry implements ITraceEntry {
         private long mTimestamp;
         private List<Layer> mRootLayers; // hierarchical representation of layers
         private List<Layer> mFlattenedLayers = null;
@@ -117,7 +119,7 @@
         /**
          * Constructs the layer hierarchy from a flattened list of layers.
          */
-        static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
+        public static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
             SparseArray<Layer> layerMap = new SparseArray<>();
             ArrayList<Layer> orphans = new ArrayList<>();
             for (LayerProto proto : protos) {
@@ -181,7 +183,7 @@
         /**
          * Checks if a region specified by {@code testRect} is covered by all visible layers.
          */
-        Result coversRegion(Rect testRect) {
+        public Result coversRegion(Rect testRect) {
             String assertionName = "coversRegion";
             Collection<Layer> layers = asFlattenedLayers();
 
@@ -224,7 +226,7 @@
          * Checks if a layer with name {@code layerName} has a visible region
          * {@code expectedVisibleRegion}.
          */
-        Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
+        public Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
             String assertionName = "hasVisibleRegion";
             String reason = "Could not find " + layerName;
             for (Layer layer : asFlattenedLayers()) {
@@ -252,7 +254,7 @@
         /**
          * Checks if a layer with name {@code layerName} is visible.
          */
-        Result isVisible(String layerName) {
+        public Result isVisible(String layerName) {
             String assertionName = "isVisible";
             String reason = "Could not find " + layerName;
             for (Layer layer : asFlattenedLayers()) {
@@ -277,24 +279,27 @@
             return mTimestamp;
         }
 
-        List<Layer> getRootLayers() {
+        public List<Layer> getRootLayers() {
             return mRootLayers;
         }
 
-        List<Layer> asFlattenedLayers() {
+        /**
+         * Returns all layers as a flattened list using a depth first traversal.
+         */
+        public List<Layer> asFlattenedLayers() {
             if (mFlattenedLayers == null) {
-                mFlattenedLayers = new ArrayList<>();
+                mFlattenedLayers = new LinkedList<>();
                 ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
                 while (!pendingLayers.isEmpty()) {
                     Layer layer = pendingLayers.remove(0);
                     mFlattenedLayers.add(layer);
-                    pendingLayers.addAll(layer.mChildren);
+                    pendingLayers.addAll(0, layer.mChildren);
                 }
             }
             return mFlattenedLayers;
         }
 
-        Rect getVisibleBounds(String layerName) {
+        public Rect getVisibleBounds(String layerName) {
             List<Layer> layers = asFlattenedLayers();
             for (Layer layer : layers) {
                 if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
@@ -308,12 +313,12 @@
     /**
      * Represents a single layer with links to its parent and child layers.
      */
-    static class Layer {
+    public static class Layer {
         @Nullable
-        LayerProto mProto;
-        List<Layer> mChildren;
+        public LayerProto mProto;
+        public List<Layer> mChildren;
         @Nullable
-        Layer mParent = null;
+        public Layer mParent = null;
 
         private Layer(LayerProto proto) {
             this.mProto = proto;
@@ -328,16 +333,16 @@
             this.mParent = parentLayer;
         }
 
-        int getId() {
+        public int getId() {
             return mProto.id;
         }
 
-        boolean isActiveBufferEmpty() {
+        public boolean isActiveBufferEmpty() {
             return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
                     || this.mProto.activeBuffer.width == 0;
         }
 
-        boolean isVisibleRegionEmpty() {
+        public boolean isVisibleRegionEmpty() {
             if (this.mProto.visibleRegion == null) {
                 return true;
             }
@@ -345,32 +350,35 @@
             return visibleRect.height() == 0 || visibleRect.width() == 0;
         }
 
-        boolean isHidden() {
+        public boolean isHidden() {
             return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
         }
 
-        boolean isVisible() {
-            return (!isActiveBufferEmpty() || isColorLayer()) &&
-                    !isHidden() && this.mProto.color.a > 0 && !isVisibleRegionEmpty();
+        public boolean isVisible() {
+            return (!isActiveBufferEmpty() || isColorLayer())
+                    && !isHidden()
+                    && this.mProto.color != null
+                    && this.mProto.color.a > 0
+                    && !isVisibleRegionEmpty();
         }
 
-        boolean isColorLayer() {
+        public boolean isColorLayer() {
             return this.mProto.type.equals("ColorLayer");
         }
 
-        boolean isRootLayer() {
+        public boolean isRootLayer() {
             return mParent == null || mParent.mProto == null;
         }
 
-        boolean isInvisible() {
+        public boolean isInvisible() {
             return !isVisible();
         }
 
-        boolean isHiddenByParent() {
+        public boolean isHiddenByParent() {
             return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
         }
 
-        String getHiddenByParentReason() {
+        public String getHiddenByParentReason() {
             String reason = "Layer " + mProto.name;
             if (isHiddenByParent()) {
                 reason += " is hidden by parent: " + mParent.mProto.name;
@@ -380,7 +388,7 @@
             return reason;
         }
 
-        String getVisibilityReason() {
+        public String getVisibilityReason() {
             String reason = "Layer " + mProto.name;
             if (isVisible()) {
                 reason += " is visible:";
@@ -399,7 +407,7 @@
                 if (isHidden()) {
                     reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
                 }
-                if (this.mProto.color.a == 0) {
+                if (this.mProto.color == null || this.mProto.color.a == 0) {
                     reason += " color.a=0";
                 }
                 if (isVisibleRegionEmpty()) {
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
index 4085810..4a5129e 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/LayersTraceSubject.java
@@ -19,9 +19,10 @@
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.annotation.Nullable;
 import android.graphics.Rect;
 
+import androidx.annotation.Nullable;
+
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.flicker.LayersTrace.Entry;
 import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
index 0a3fe3c..241a1c0 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/TransitionRunner.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
+import static com.android.server.wm.flicker.monitor.ITransitionMonitor.OUTPUT_DIR;
+
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.wm.flicker.monitor.ITransitionMonitor;
@@ -89,7 +91,7 @@
  * }
  * </pre>
  */
-class TransitionRunner {
+public class TransitionRunner {
     private static final String TAG = "FLICKER";
     private final ScreenRecorder mScreenRecorder;
     private final WindowManagerTraceMonitor mWmTraceMonitor;
@@ -128,8 +130,12 @@
         mTestTag = builder.mTestTag;
     }
 
-    static TransitionBuilder newBuilder() {
-        return new TransitionBuilder();
+    public static TransitionBuilder newBuilder() {
+        return newBuilder(OUTPUT_DIR.toString());
+    }
+
+    public static TransitionBuilder newBuilder(String outputDir) {
+        return new TransitionBuilder(outputDir);
     }
 
     /**
@@ -138,7 +144,7 @@
      *
      * @return itself
      */
-    TransitionRunner run() {
+    public TransitionRunner run() {
         mResults = new ArrayList<>();
         mAllRunsMonitors.forEach(ITransitionMonitor::start);
         mBeforeAlls.forEach(Runnable::run);
@@ -159,8 +165,7 @@
         mAfterAlls.forEach(Runnable::run);
         mAllRunsMonitors.forEach(monitor -> {
             monitor.stop();
-            Path path = monitor.save(mTestTag);
-            Log.e(TAG, "Video saved to " + path.toString());
+            monitor.save(mTestTag);
         });
         return this;
     }
@@ -170,7 +175,7 @@
      *
      * @return list of transition results.
      */
-    List<TransitionResult> getResults() {
+    public List<TransitionResult> getResults() {
         if (mResults == null) {
             throw new IllegalStateException("Results do not exist!");
         }
@@ -182,7 +187,7 @@
      *
      * @return list of transition results.
      */
-    void deleteResults() {
+    public void deleteResults() {
         if (mResults == null) {
             return;
         }
@@ -228,33 +233,33 @@
     @VisibleForTesting
     public static class TransitionResult {
         @Nullable
-        final Path layersTrace;
+        public final Path layersTrace;
         @Nullable
-        final Path windowManagerTrace;
+        public final Path windowManagerTrace;
         @Nullable
-        final Path screenCaptureVideo;
+        public final Path screenCaptureVideo;
         private boolean flaggedForSaving;
 
-        TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
+        public TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
                 @Nullable Path screenCaptureVideo) {
             this.layersTrace = layersTrace;
             this.windowManagerTrace = windowManagerTrace;
             this.screenCaptureVideo = screenCaptureVideo;
         }
 
-        void flagForSaving() {
+        public void flagForSaving() {
             flaggedForSaving = true;
         }
 
-        boolean canDelete() {
+        public boolean canDelete() {
             return !flaggedForSaving;
         }
 
-        boolean layersTraceExists() {
+        public boolean layersTraceExists() {
             return layersTrace != null && layersTrace.toFile().exists();
         }
 
-        byte[] getLayersTrace() {
+        public byte[] getLayersTrace() {
             try {
                 return Files.toByteArray(this.layersTrace.toFile());
             } catch (IOException e) {
@@ -262,11 +267,11 @@
             }
         }
 
-        Path getLayersTracePath() {
+        public Path getLayersTracePath() {
             return layersTrace;
         }
 
-        boolean windowManagerTraceExists() {
+        public boolean windowManagerTraceExists() {
             return windowManagerTrace != null && windowManagerTrace.toFile().exists();
         }
 
@@ -278,19 +283,19 @@
             }
         }
 
-        Path getWindowManagerTracePath() {
+        public Path getWindowManagerTracePath() {
             return windowManagerTrace;
         }
 
-        boolean screenCaptureVideoExists() {
+        public boolean screenCaptureVideoExists() {
             return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
         }
 
-        Path screenCaptureVideoPath() {
+        public Path screenCaptureVideoPath() {
             return screenCaptureVideo;
         }
 
-        void delete() {
+        public void delete() {
             if (layersTraceExists()) layersTrace.toFile().delete();
             if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
             if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
@@ -300,7 +305,7 @@
     /**
      * Builds a {@link TransitionRunner} instance.
      */
-    static class TransitionBuilder {
+    public static class TransitionBuilder {
         private ScreenRecorder mScreenRecorder;
         private WindowManagerTraceMonitor mWmTraceMonitor;
         private LayersTraceMonitor mLayersTraceMonitor;
@@ -323,15 +328,15 @@
 
         private boolean mRecordAllRuns = false;
 
-        TransitionBuilder() {
+        public TransitionBuilder(String outputDir) {
             mScreenRecorder = new ScreenRecorder();
-            mWmTraceMonitor = new WindowManagerTraceMonitor();
-            mLayersTraceMonitor = new LayersTraceMonitor();
+            mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir);
+            mLayersTraceMonitor = new LayersTraceMonitor(outputDir);
             mFrameStatsMonitor = new
                     WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation());
         }
 
-        TransitionRunner build() {
+        public TransitionRunner build() {
             if (mCaptureWindowManagerTrace) {
                 mPerRunMonitors.add(mWmTraceMonitor);
             }
@@ -355,52 +360,52 @@
             return new TransitionRunner(this);
         }
 
-        TransitionBuilder runBeforeAll(Runnable runnable) {
+        public TransitionBuilder runBeforeAll(Runnable runnable) {
             mBeforeAlls.add(runnable);
             return this;
         }
 
-        TransitionBuilder runBefore(Runnable runnable) {
+        public TransitionBuilder runBefore(Runnable runnable) {
             mBefores.add(runnable);
             return this;
         }
 
-        TransitionBuilder run(Runnable runnable) {
+        public TransitionBuilder run(Runnable runnable) {
             mTransitions.add(runnable);
             return this;
         }
 
-        TransitionBuilder runAfter(Runnable runnable) {
+        public TransitionBuilder runAfter(Runnable runnable) {
             mAfters.add(runnable);
             return this;
         }
 
-        TransitionBuilder runAfterAll(Runnable runnable) {
+        public TransitionBuilder runAfterAll(Runnable runnable) {
             mAfterAlls.add(runnable);
             return this;
         }
 
-        TransitionBuilder repeat(int iterations) {
+        public TransitionBuilder repeat(int iterations) {
             mIterations = iterations;
             return this;
         }
 
-        TransitionBuilder skipWindowManagerTrace() {
+        public TransitionBuilder skipWindowManagerTrace() {
             mCaptureWindowManagerTrace = false;
             return this;
         }
 
-        TransitionBuilder skipLayersTrace() {
+        public TransitionBuilder skipLayersTrace() {
             mCaptureLayersTrace = false;
             return this;
         }
 
-        TransitionBuilder includeJankyRuns() {
+        public TransitionBuilder includeJankyRuns() {
             mRunJankFree = false;
             return this;
         }
 
-        TransitionBuilder recordEachRun() {
+        public TransitionBuilder recordEachRun() {
             if (mRecordAllRuns) {
                 throw new IllegalArgumentException("Invalid option with recordAllRuns");
             }
@@ -408,7 +413,7 @@
             return this;
         }
 
-        TransitionBuilder recordAllRuns() {
+        public TransitionBuilder recordAllRuns() {
             if (mRecordEachRun) {
                 throw new IllegalArgumentException("Invalid option with recordEachRun");
             }
@@ -416,7 +421,11 @@
             return this;
         }
 
-        TransitionBuilder withTag(String testTag) {
+        public TransitionBuilder withTag(String testTag) {
+            if (testTag.contains(" ")) {
+                throw new IllegalArgumentException("The test tag can not contain spaces since it "
+                        + "is a part of the file name");
+            }
             mTestTag = testTag;
             return this;
         }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
index e3592eb..412e72d8 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowManagerTrace.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.nano.AppWindowTokenProto;
@@ -58,7 +58,7 @@
      * @param data   binary proto data
      * @param source Path to source of data for additional debug information
      */
-    static WindowManagerTrace parseFrom(byte[] data, Path source) {
+    public static WindowManagerTrace parseFrom(byte[] data, Path source) {
         List<Entry> entries = new ArrayList<>();
 
         WindowManagerTraceFileProto fileProto;
@@ -73,7 +73,7 @@
         return new WindowManagerTrace(entries, source);
     }
 
-    static WindowManagerTrace parseFrom(byte[] data) {
+    public static WindowManagerTrace parseFrom(byte[] data) {
         return parseFrom(data, null);
     }
 
@@ -81,7 +81,7 @@
         return mEntries;
     }
 
-    Entry getEntry(long timestamp) {
+    public Entry getEntry(long timestamp) {
         Optional<Entry> entry = mEntries.stream()
                 .filter(e -> e.getTimestamp() == timestamp)
                 .findFirst();
@@ -91,17 +91,17 @@
         return entry.get();
     }
 
-    Optional<Path> getSource() {
+    public Optional<Path> getSource() {
         return Optional.ofNullable(mSource);
     }
 
     /**
      * Represents a single WindowManager trace entry.
      */
-    static class Entry implements ITraceEntry {
+    public static class Entry implements ITraceEntry {
         private final WindowManagerTraceProto mProto;
 
-        Entry(WindowManagerTraceProto proto) {
+        public Entry(WindowManagerTraceProto proto) {
             mProto = proto;
         }
 
@@ -162,7 +162,7 @@
         /**
          * Checks if aboveAppWindow with {@code windowTitle} is visible.
          */
-        Result isAboveAppWindowVisible(String windowTitle) {
+        public Result isAboveAppWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].aboveAppWindows;
@@ -173,7 +173,7 @@
         /**
          * Checks if belowAppWindow with {@code windowTitle} is visible.
          */
-        Result isBelowAppWindowVisible(String windowTitle) {
+        public Result isBelowAppWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].belowAppWindows;
@@ -185,7 +185,7 @@
         /**
          * Checks if imeWindow with {@code windowTitle} is visible.
          */
-        Result isImeWindowVisible(String windowTitle) {
+        public Result isImeWindowVisible(String windowTitle) {
             WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
                     .rootWindowContainer
                     .displays[DEFAULT_DISPLAY].imeWindows;
@@ -197,7 +197,7 @@
         /**
          * Checks if app window with {@code windowTitle} is on top.
          */
-        Result isVisibleAppWindowOnTop(String windowTitle) {
+        public Result isVisibleAppWindowOnTop(String windowTitle) {
             String topAppWindow = getTopVisibleAppWindow();
             boolean success = topAppWindow.contains(windowTitle);
             String reason = "wanted=" + windowTitle + " found=" + topAppWindow;
@@ -207,7 +207,7 @@
         /**
          * Checks if app window with {@code windowTitle} is visible.
          */
-        Result isAppWindowVisible(String windowTitle) {
+        public Result isAppWindowVisible(String windowTitle) {
             final String assertionName = "isAppWindowVisible";
             boolean titleFound = false;
             StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
index c54396f..3d25fbe 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WindowUtils.java
@@ -28,9 +28,9 @@
 /**
  * Helper functions to retrieve system window sizes and positions.
  */
-class WindowUtils {
+public class WindowUtils {
 
-    static Rect getDisplayBounds() {
+    public static Rect getDisplayBounds() {
         Point display = new Point();
         WindowManager wm =
                 (WindowManager) InstrumentationRegistry.getContext().getSystemService(
@@ -46,7 +46,7 @@
         return wm.getDefaultDisplay().getRotation();
     }
 
-    static Rect getDisplayBounds(int requestedRotation) {
+    public static Rect getDisplayBounds(int requestedRotation) {
         Rect displayBounds = getDisplayBounds();
         int currentDisplayRotation = getCurrentRotation();
 
@@ -66,7 +66,7 @@
     }
 
 
-    static Rect getAppPosition(int requestedRotation) {
+    public static Rect getAppPosition(int requestedRotation) {
         Rect displayBounds = getDisplayBounds();
         int currentDisplayRotation = getCurrentRotation();
 
@@ -85,7 +85,7 @@
         return new Rect(0, 0, displayBounds.width(), displayBounds.height());
     }
 
-    static Rect getStatusBarPosition(int requestedRotation) {
+    public static Rect getStatusBarPosition(int requestedRotation) {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         String resourceName;
         Rect displayBounds = getDisplayBounds();
@@ -104,7 +104,7 @@
         return new Rect(0, 0, width, height);
     }
 
-    static Rect getNavigationBarPosition(int requestedRotation) {
+    public static Rect getNavigationBarPosition(int requestedRotation) {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         Rect displayBounds = getDisplayBounds();
         int displayWidth = Math.min(displayBounds.width(), displayBounds.height());
@@ -129,13 +129,13 @@
         }
     }
 
-    static int getNavigationBarHeight() {
+    public static int getNavigationBarHeight() {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
         return resources.getDimensionPixelSize(resourceId);
     }
 
-    static int getDockedStackDividerInset() {
+    public static int getDockedStackDividerInset() {
         Resources resources = InstrumentationRegistry.getContext().getResources();
         int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen",
                 "android");
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
index e76da6e..064cc27 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/WmTraceSubject.java
@@ -19,7 +19,7 @@
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 import com.android.server.wm.flicker.Assertions.Result;
 import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java
similarity index 95%
rename from tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java
rename to tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java
index e00a247..6821ff0 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/AutomationUtils.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/helpers/AutomationUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.wm.flicker;
+package com.android.server.wm.flicker.helpers;
 
 import static android.os.SystemClock.sleep;
 import static android.system.helpers.OverviewHelper.isRecentsInLauncher;
@@ -44,6 +44,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.server.wm.flicker.WindowUtils;
+
 /**
  * Collection of UI Automation helper functions.
  */
@@ -70,14 +72,14 @@
      * This removes some delays when using the UIAutomator library required to create fast UI
      * transitions.
      */
-    static void setFastWait() {
+    public static void setFastWait() {
         Configurator.getInstance().setWaitForIdleTimeout(0);
     }
 
     /**
      * Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior.
      */
-    static void setDefaultWait() {
+    public static void setDefaultWait() {
         Configurator.getInstance().setWaitForIdleTimeout(10000);
     }
 
@@ -124,7 +126,7 @@
         device.waitForIdle();
     }
 
-    static void clearRecents(UiDevice device) {
+    public static void clearRecents(UiDevice device) {
         if (isQuickstepEnabled(device)) {
             openQuickstep(device);
 
@@ -201,7 +203,7 @@
         sleep(2000);
     }
 
-    static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
+    public static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
         BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
         UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
         assertNotNull("Unable to find Split screen divider", divider);
@@ -218,7 +220,7 @@
         sleep(2000);
     }
 
-    static void closePipWindow(UiDevice device) {
+    public static void closePipWindow(UiDevice device) {
         UiObject2 pipWindow = device.findObject(
                 By.res(SYSTEMUI_PACKAGE, "background"));
         pipWindow.click();
@@ -229,7 +231,7 @@
         sleep(2000);
     }
 
-    static void expandPipWindow(UiDevice device) {
+    public static void expandPipWindow(UiDevice device) {
         UiObject2 pipWindow = device.findObject(
                 By.res(SYSTEMUI_PACKAGE, "background"));
         pipWindow.click();
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
index c55d068..da75b3e 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/LayersTraceMonitor.java
@@ -16,21 +16,22 @@
 
 package com.android.server.wm.flicker.monitor;
 
-import android.os.IBinder;
-import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
 
 /**
  * Captures Layers trace from SurfaceFlinger.
  */
 public class LayersTraceMonitor extends TraceMonitor {
-    private static final String TAG = "LayersTraceMonitor";
-    private IBinder mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+    private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
 
     public LayersTraceMonitor() {
-        traceFileName = "layers_trace.pb";
+        this(OUTPUT_DIR.toString());
+    }
+
+    public LayersTraceMonitor(String outputDir) {
+        super(outputDir, "layers_trace.pb");
     }
 
     @Override
@@ -45,30 +46,19 @@
 
     @Override
     public boolean isEnabled() throws RemoteException {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken("android.ui.ISurfaceComposer");
-        mSurfaceFlinger.transact(/* LAYER_TRACE_STATUS_CODE */ 1026,
-                data, reply, 0 /* flags */);
-        return reply.readBoolean();
+        try {
+            return mWm.isLayerTracing();
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return false;
     }
 
     private void setEnabled(boolean isEnabled) {
-        Parcel data = null;
         try {
-            if (mSurfaceFlinger != null) {
-                data = Parcel.obtain();
-                data.writeInterfaceToken("android.ui.ISurfaceComposer");
-                data.writeInt(isEnabled ? 1 : 0);
-                mSurfaceFlinger.transact( /* LAYER_TRACE_CONTROL_CODE */ 1025,
-                        data, null, 0 /* flags */);
-            }
+            mWm.setLayerTracing(isEnabled);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not set layer tracing." + e.toString());
-        } finally {
-            if (data != null) {
-                data.recycle();
-            }
+            e.printStackTrace();
         }
     }
 }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
index 4787586..dce1c27 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/ScreenRecorder.java
@@ -20,25 +20,25 @@
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 
 /**
  * Captures screen contents and saves it as a mp4 video file.
  */
 public class ScreenRecorder implements ITransitionMonitor {
     @VisibleForTesting
-    static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
+    public static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
     private static final String TAG = "FLICKER";
     private Thread recorderThread;
 
     @VisibleForTesting
-    static Path getPath(String testTag) {
+    public static Path getPath(String testTag) {
         return OUTPUT_DIR.resolve(testTag + ".mp4");
     }
 
@@ -69,8 +69,10 @@
     @Override
     public Path save(String testTag) {
         try {
-            return Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
+            Path targetPath = Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
                     REPLACE_EXISTING);
+            Log.i(TAG, "Video saved to " + targetPath.toString());
+            return targetPath;
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
index 0e154ec..1ba36bb 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/TraceMonitor.java
@@ -20,7 +20,7 @@
 
 import android.os.RemoteException;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
 
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -34,9 +34,15 @@
     public static final String TAG = "FLICKER";
     private static final String TRACE_DIR = "/data/misc/wmtrace/";
 
-    String traceFileName;
+    private Path mOutputDir;
+    public String mTraceFileName;
 
-    abstract boolean isEnabled() throws RemoteException;
+    public abstract boolean isEnabled() throws RemoteException;
+
+    public TraceMonitor(String outputDir, String traceFileName) {
+        mOutputDir = Paths.get(outputDir);
+        mTraceFileName = traceFileName;
+    }
 
     /**
      * Saves trace file to the external storage directory suffixing the name with the testtag
@@ -53,14 +59,16 @@
     public Path save(String testTag) {
         OUTPUT_DIR.toFile().mkdirs();
         Path traceFileCopy = getOutputTraceFilePath(testTag);
+
+        // Read the input stream fully.
         String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR,
-                traceFileName, traceFileCopy.toString());
+                mTraceFileName, traceFileCopy.toString());
         runShellCommand(copyCommand);
         return traceFileCopy;
     }
 
     @VisibleForTesting
-    Path getOutputTraceFilePath(String testTag) {
-        return OUTPUT_DIR.resolve(traceFileName + "_" + testTag);
+    public Path getOutputTraceFilePath(String testTag) {
+        return mOutputDir.resolve(mTraceFileName + "_" + testTag);
     }
 }
diff --git a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
index ae160b68..11de4aa 100644
--- a/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
+++ b/tests/FlickerTests/lib/src/com/android/server/wm/flicker/monitor/WindowManagerTraceMonitor.java
@@ -24,16 +24,20 @@
  * Captures WindowManager trace from WindowManager.
  */
 public class WindowManagerTraceMonitor extends TraceMonitor {
-    private IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+    private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
 
     public WindowManagerTraceMonitor() {
-        traceFileName = "wm_trace.pb";
+        this(OUTPUT_DIR.toString());
+    }
+
+    public WindowManagerTraceMonitor(String outputDir) {
+        super(outputDir, "wm_trace.pb");
     }
 
     @Override
     public void start() {
         try {
-            wm.startWindowTrace();
+            mWm.startWindowTrace();
         } catch (RemoteException e) {
             throw new RuntimeException("Could not start trace", e);
         }
@@ -42,7 +46,7 @@
     @Override
     public void stop() {
         try {
-            wm.stopWindowTrace();
+            mWm.stopWindowTrace();
         } catch (RemoteException e) {
             throw new RuntimeException("Could not stop trace", e);
         }
@@ -50,6 +54,6 @@
 
     @Override
     public boolean isEnabled() throws RemoteException{
-        return wm.isWindowTraceEnabled();
+        return mWm.isWindowTraceEnabled();
     }
 }
diff --git a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
index dd6fed0..f312384 100644
--- a/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
+++ b/tests/FlickerTests/lib/test/src/com/android/server/wm/flicker/monitor/WindowAnimationFrameStatsMonitorTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker.monitor;
 
-import static com.android.server.wm.flicker.AutomationUtils.wakeUpAndGoToHomeScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.wakeUpAndGoToHomeScreen;
 
 import androidx.test.InstrumentationRegistry;
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
index 65888ac..5cf2c1c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
@@ -19,12 +19,12 @@
 import static android.os.SystemClock.sleep;
 import static android.view.Surface.rotationToString;
 
-import static com.android.server.wm.flicker.AutomationUtils.clearRecents;
-import static com.android.server.wm.flicker.AutomationUtils.closePipWindow;
-import static com.android.server.wm.flicker.AutomationUtils.exitSplitScreen;
-import static com.android.server.wm.flicker.AutomationUtils.expandPipWindow;
-import static com.android.server.wm.flicker.AutomationUtils.launchSplitScreen;
-import static com.android.server.wm.flicker.AutomationUtils.stopPackage;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.clearRecents;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.closePipWindow;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.exitSplitScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.expandPipWindow;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.launchSplitScreen;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.stopPackage;
 
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
+import com.android.server.wm.flicker.helpers.AutomationUtils;
 
 /**
  * Collection of common transitions which can be used to test different apps or scenarios.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
index 00e11c0..8c9d6b4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm.flicker;
 
-import static com.android.server.wm.flicker.AutomationUtils.setDefaultWait;
+import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 88d92c4..0b75039 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -23,6 +23,7 @@
         "androidx.test.rules",
         "services.core",
         "services.net",
+        "truth-prebuilt",
     ],
     libs: ["android.test.runner"],
     jni_libs: [
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index c42201f..79f5095 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,8 @@
 
 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -44,6 +42,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.server.PackageWatchdog.HealthCheckState;
 import com.android.server.PackageWatchdog.MonitoredPackage;
 import com.android.server.PackageWatchdog.PackageHealthObserver;
 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
@@ -65,9 +64,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
-// TODO: Write test without using PackageWatchdog#getPackages. Just rely on
-// behavior of observers receiving crash notifications or not to determine if it's registered
-// TODO: Use Truth in tests.
 /**
  * Test PackageWatchdog.
  */
@@ -83,6 +79,7 @@
     private static final String OBSERVER_NAME_4 = "observer4";
     private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
     private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
+    private final TestClock mTestClock = new TestClock();
     private TestLooper mTestLooper;
     private Context mSpyContext;
     @Mock
@@ -114,78 +111,105 @@
         dropShellPermissions();
     }
 
-    /**
-     * Test registration, unregistration, package expiry and duration reduction
-     */
     @Test
-    public void testRegistration() throws Exception {
+    public void testRegistration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // The failed packages should be the same as the registered ones to ensure registration is
+        // done successfully
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    @Test
+    public void testRegistration_multiObservers() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
 
-        // Start observing for observer1 which will be unregistered
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
-        // Start observing for observer2 which will expire
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
-        // Start observing for observer3 which will have expiry duration reduced
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE)));
 
-        // Verify packages observed at start
-        // 1
-        assertEquals(1, watchdog.getPackages(observer1).size());
-        assertTrue(watchdog.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog.getPackages(observer2).size());
-        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+        // The failed packages should be the same as the registered ones to ensure registration is
+        // done successfully
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
+    }
 
-        // Then unregister observer1
-        watchdog.unregisterHealthObserver(observer1);
+    @Test
+    public void testUnregistration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
-        // Verify observer2 and observer3 left
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertEquals(2, watchdog.getPackages(observer2).size());
-        assertTrue(watchdog.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog.getPackages(observer2).contains(APP_B));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.unregisterHealthObserver(observer);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
-        // Then advance time a little and run messages in Handlers so observer2 expires
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        // We should have no failed packages to ensure unregistration is done successfully
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
 
-        // Verify observer3 left with reduced expiry duration
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertNull(watchdog.getPackages(observer2));
-        // 3
-        assertEquals(1, watchdog.getPackages(observer3).size());
-        assertTrue(watchdog.getPackages(observer3).contains(APP_A));
+    @Test
+    public void testUnregistration_multiObservers() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
-        // Then advance time some more and run messages in Handlers so observer3 expires
-        Thread.sleep(LONG_DURATION);
-        mTestLooper.dispatchAll();
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.unregisterHealthObserver(observer2);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
-        // Verify observer3 expired
-        // 1
-        assertNull(watchdog.getPackages(observer1));
-        // 2
-        assertNull(watchdog.getPackages(observer2));
-        // 3
-        assertNull(watchdog.getPackages(observer3));
+        // observer1 should receive failed packages as intended.
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        // observer2 should have no failed packages to ensure unregistration is done successfully
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    @Test
+    public void testExpiration_singleObserver() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        moveTimeForwardAndDispatch(SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should have no failed packages for the fatal failure is raised after expiration
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    @Test
+    public void testExpiration_multiObservers() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+        TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
+
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+        watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+        moveTimeForwardAndDispatch(SHORT_DURATION);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should have no failed packages for the fatal failure is raised after expiration
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        // We should have failed packages since observer2 hasn't expired
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     /** Observing already observed package extends the observation time. */
     @Test
-    public void testObserveAlreadyObservedPackage() throws Exception {
+    public void testObserveAlreadyObservedPackage() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
 
@@ -193,66 +217,47 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time half-way
-        Thread.sleep(SHORT_DURATION / 2);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION / 2);
 
         // Start observing APP_A again
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then advance time such that it should have expired were it not for the second observation
-        Thread.sleep((SHORT_DURATION / 2) + 1);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
 
-        // Verify that APP_A not expired since second observation extended the time
-        assertEquals(1, watchdog.getPackages(observer).size());
-        assertTrue(watchdog.getPackages(observer).contains(APP_A));
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // Verify that we receive failed packages as expected for APP_A not expired
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     /**
      * Test package observers are persisted and loaded on startup
      */
     @Test
-    public void testPersistence() throws Exception {
+    public void testPersistence() {
         PackageWatchdog watchdog1 = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
 
         watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
-
-        // Verify 2 observers are registered and saved internally
-        // 1
-        assertEquals(1, watchdog1.getPackages(observer1).size());
-        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog1.getPackages(observer2).size());
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
-
         // Then advance time and run IO Handler so file is saved
         mTestLooper.dispatchAll();
-
         // Then start a new watchdog
         PackageWatchdog watchdog2 = createWatchdog();
-
-        // Verify the new watchdog loads observers on startup but nothing registered
-        assertEquals(0, watchdog2.getPackages(observer1).size());
-        assertEquals(0, watchdog2.getPackages(observer2).size());
-        // Verify random observer not saved returns null
-        assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3)));
-
-        // Then register observer1
+        // Then resume observer1 and observer2
         watchdog2.registerHealthObserver(observer1);
         watchdog2.registerHealthObserver(observer2);
+        raiseFatalFailureAndDispatch(watchdog2,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE)));
 
-        // Verify 2 observers are registered after reload
-        // 1
-        assertEquals(1, watchdog1.getPackages(observer1).size());
-        assertTrue(watchdog1.getPackages(observer1).contains(APP_A));
-        // 2
-        assertEquals(2, watchdog1.getPackages(observer2).size());
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_A));
-        assertTrue(watchdog1.getPackages(observer2).contains(APP_B));
+        // We should receive failed packages as expected to ensure observers are persisted and
+        // resumed correctly
+        assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
+        assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
     }
 
     /**
@@ -276,8 +281,8 @@
         mTestLooper.dispatchAll();
 
         // Verify that observers are not notified
-        assertEquals(0, observer1.mFailedPackages.size());
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
     }
 
     /**
@@ -295,16 +300,12 @@
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
 
         // Then fail APP_C (not observed) above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)));
 
         // Verify that observers are not notified
-        assertEquals(0, observer1.mFailedPackages.size());
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
+        assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
     }
 
     /**
@@ -329,16 +330,11 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A (different version) above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(
-                            new VersionedPackage(APP_A, differentVersionCode)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)));
 
         // Verify that observers are not notified
-        assertEquals(0, observer.mFailedPackages.size());
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
     }
 
 
@@ -368,33 +364,26 @@
                 SHORT_DURATION);
 
         // Then fail all apps above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
-                    new VersionedPackage(APP_B, VERSION_CODE),
-                    new VersionedPackage(APP_C, VERSION_CODE),
-                    new VersionedPackage(APP_D, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
+                        new VersionedPackage(APP_B, VERSION_CODE),
+                        new VersionedPackage(APP_C, VERSION_CODE),
+                        new VersionedPackage(APP_D, VERSION_CODE)));
 
         // Verify least impact observers are notifed of package failures
-        List<String> observerNonePackages = observerNone.mFailedPackages;
-        List<String> observerHighPackages = observerHigh.mFailedPackages;
-        List<String> observerMidPackages = observerMid.mFailedPackages;
-        List<String> observerLowPackages = observerLow.mFailedPackages;
+        List<String> observerNonePackages = observerNone.mMitigatedPackages;
+        List<String> observerHighPackages = observerHigh.mMitigatedPackages;
+        List<String> observerMidPackages = observerMid.mMitigatedPackages;
+        List<String> observerLowPackages = observerLow.mMitigatedPackages;
 
         // APP_D failure observed by only observerNone is not caught cos its impact is none
-        assertEquals(0, observerNonePackages.size());
+        assertThat(observerNonePackages).isEmpty();
         // APP_C failure is caught by observerHigh cos it's the lowest impact observer
-        assertEquals(1, observerHighPackages.size());
-        assertEquals(APP_C, observerHighPackages.get(0));
+        assertThat(observerHighPackages).containsExactly(APP_C);
         // APP_B failure is caught by observerMid cos it's the lowest impact observer
-        assertEquals(1, observerMidPackages.size());
-        assertEquals(APP_B, observerMidPackages.get(0));
+        assertThat(observerMidPackages).containsExactly(APP_B);
         // APP_A failure is caught by observerLow cos it's the lowest impact observer
-        assertEquals(1, observerLowPackages.size());
-        assertEquals(APP_A, observerLowPackages.get(0));
+        assertThat(observerLowPackages).containsExactly(APP_A);
     }
 
     /**
@@ -421,66 +410,51 @@
         watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then fail APP_A above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerFirst is notifed
-        assertEquals(1, observerFirst.mFailedPackages.size());
-        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, next action it has is high impact
         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerSecond is notifed cos it has least impact
-        assertEquals(1, observerSecond.mFailedPackages.size());
-        assertEquals(APP_A, observerSecond.mFailedPackages.get(0));
-        assertEquals(0, observerFirst.mFailedPackages.size());
+        assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerFirst.mMitigatedPackages).isEmpty();
 
         // After observerSecond handles failure, it has no further actions
         observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only observerFirst is notifed cos it has the only action
-        assertEquals(1, observerFirst.mFailedPackages.size());
-        assertEquals(APP_A, observerFirst.mFailedPackages.get(0));
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
 
         // After observerFirst handles failure, it too has no further actions
         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
-        observerFirst.mFailedPackages.clear();
-        observerSecond.mFailedPackages.clear();
+        observerFirst.mMitigatedPackages.clear();
+        observerSecond.mMitigatedPackages.clear();
 
         // Then fail APP_A again above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify no observer is notified cos no actions left
-        assertEquals(0, observerFirst.mFailedPackages.size());
-        assertEquals(0, observerSecond.mFailedPackages.size());
+        assertThat(observerFirst.mMitigatedPackages).isEmpty();
+        assertThat(observerSecond.mMitigatedPackages).isEmpty();
     }
 
     /**
@@ -499,17 +473,12 @@
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then fail APP_A above the threshold
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
-            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
-        }
-
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
 
         // Verify only one observer is notifed
-        assertEquals(1, observer1.mFailedPackages.size());
-        assertEquals(APP_A, observer1.mFailedPackages.get(0));
-        assertEquals(0, observer2.mFailedPackages.size());
+        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
+        assertThat(observer2.mMitigatedPackages).isEmpty();
     }
 
     /**
@@ -538,9 +507,7 @@
 
         // Verify we requested health checks for APP_A and APP_B
         List<String> requestedPackages = controller.getRequestedPackages();
-        assertEquals(2, requestedPackages.size());
-        assertEquals(APP_A, requestedPackages.get(0));
-        assertEquals(APP_B, requestedPackages.get(1));
+        assertThat(requestedPackages).containsExactly(APP_A, APP_B);
 
         // Then health check passed for APP_A (observer1 is aware)
         controller.setPackagePassed(APP_A);
@@ -552,23 +519,19 @@
         watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
 
         // Then expire observers
-        Thread.sleep(SHORT_DURATION);
-        // Run handler so package failures are dispatched to observers
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify we cancelled all requests on expiry
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Verify observer1 is not notified
-        assertEquals(0, observer1.mFailedPackages.size());
+        assertThat(observer1.mMitigatedPackages).isEmpty();
 
         // Verify observer2 is notifed because health checks for APP_B never passed
-        assertEquals(1, observer2.mFailedPackages.size());
-        assertEquals(APP_B, observer2.mFailedPackages.get(0));
+        assertThat(observer2.mMitigatedPackages).containsExactly(APP_B);
 
         // Verify observer3 is notifed because health checks for APP_A did not pass before expiry
-        assertEquals(1, observer3.mFailedPackages.size());
-        assertEquals(APP_A, observer3.mFailedPackages.get(0));
+        assertThat(observer3.mMitigatedPackages).containsExactly(APP_A);
     }
 
     /**
@@ -595,9 +558,7 @@
 
         // Verify we requested health checks for APP_A and APP_B
         List<String> requestedPackages = controller.getRequestedPackages();
-        assertEquals(2, requestedPackages.size());
-        assertEquals(APP_A, requestedPackages.get(0));
-        assertEquals(APP_B, requestedPackages.get(1));
+        assertThat(requestedPackages).containsExactly(APP_A, APP_B);
 
         // Disable explicit health checks (marks APP_A and APP_B as passed)
         setExplicitHealthCheckEnabled(false);
@@ -606,14 +567,13 @@
         mTestLooper.dispatchAll();
 
         // Verify all checks are cancelled
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Then expire APP_A
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify APP_A is not failed (APP_B) is not expired yet
-        assertEquals(0, observer.mFailedPackages.size());
+        assertThat(observer.mMitigatedPackages).isEmpty();
 
         // Re-enable explicit health checks
         setExplicitHealthCheckEnabled(true);
@@ -622,7 +582,7 @@
         mTestLooper.dispatchAll();
 
         // Verify no requests are made cos APP_A is expired and APP_B was marked as passed
-        assertEquals(0, controller.getRequestedPackages().size());
+        assertThat(controller.getRequestedPackages()).isEmpty();
 
         // Then set new supported packages
         controller.setSupportedPackages(Arrays.asList(APP_C));
@@ -634,16 +594,13 @@
 
         // Verify requests are only made for APP_C
         requestedPackages = controller.getRequestedPackages();
-        assertEquals(1, requestedPackages.size());
-        assertEquals(APP_C, requestedPackages.get(0));
+        assertThat(requestedPackages).containsExactly(APP_C);
 
         // Then expire APP_A and APP_C
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify only APP_C is failed because explicit health checks was not supported for APP_A
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_C, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_C);
     }
 
     /**
@@ -664,21 +621,19 @@
         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
 
         // Then APP_A has exceeded health check duration
-        Thread.sleep(SHORT_DURATION);
-        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(SHORT_DURATION);
 
         // Verify that health check is failed
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_A, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
 
         // Then clear failed packages and start observing a random package so requests are synced
         // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again
         // this time due to package expiry.
-        observer.mFailedPackages.clear();
+        observer.mMitigatedPackages.clear();
         watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
 
         // Verify that health check failure is not notified again
-        assertTrue(observer.mFailedPackages.isEmpty());
+        assertThat(observer.mMitigatedPackages).isEmpty();
     }
 
     /** Tests {@link MonitoredPackage} health check state transitions. */
@@ -694,36 +649,38 @@
 
         // Verify transition: inactive -> active -> passed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked());
+        assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify still inactive, until we #setHealthCheckActiveLocked
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now active
-        assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION));
+        assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+                HealthCheckState.ACTIVE);
         // Verify now passed
-        assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked());
+        assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED);
 
         // Verify transition: inactive -> active -> failed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked());
+        assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now active
-        assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION));
+        assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
+                HealthCheckState.ACTIVE);
         // Verify now failed
-        assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED);
 
         // Verify transition: inactive -> failed
         // Verify initially inactive
-        assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked());
+        assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
         // Verify now failed because package expired
-        assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION));
+        assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED);
         // Verify remains failed even when asked to pass
-        assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked());
+        assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED);
 
         // Verify transition: passed
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked());
+        assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED);
         // Verify remains passed even if health check fails
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION));
+        assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED);
         // Verify remains passed even if package expires
-        assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION));
+        assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED);
     }
 
     @Test
@@ -742,8 +699,142 @@
         mTestLooper.dispatchAll();
 
         // Verify the NetworkStack observer is notified
-        assertEquals(1, observer.mFailedPackages.size());
-        assertEquals(APP_A, observer.mFailedPackages.get(0));
+        assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
+    }
+
+    /** Test default values are used when device property is invalid. */
+    @Test
+    public void testInvalidConfig_watchdogTriggerFailureCount() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(-1), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+        // Fail APP_A below the threshold which should not trigger package failures
+        for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
+            watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        }
+        mTestLooper.dispatchAll();
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+        // One more to trigger the package failure
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /** Test default values are used when device property is invalid. */
+    @Test
+    public void testInvalidConfig_watchdogTriggerDurationMillis() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(2), /*makeDefault*/false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+                Integer.toString(-1), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We shouldn't receive APP_A since the interval of 2 failures is greater than
+        // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We should receive APP_B since the interval of 2 failures is less than
+        // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B);
+    }
+
+    /**
+     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+     * an invalid durationMs.
+     */
+    @Test
+    public void testInvalidMonitoringDuration_beforeExpiry() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
+        // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
+        // small timeouts.
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should receive APP_A since the observer hasn't expired
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /**
+     * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
+     * an invalid durationMs.
+     */
+    @Test
+    public void testInvalidMonitoringDuration_afterExpiry() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+        moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
+        raiseFatalFailureAndDispatch(watchdog,
+                Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+
+        // We should receive nothing since the observer has expired
+        assertThat(observer.mHealthCheckFailedPackages).isEmpty();
+    }
+
+    /** Test we are notified when enough failures are triggered within any window. */
+    @Test
+    public void testFailureTriggerWindow() {
+        adoptShellPermissions(
+                Manifest.permission.WRITE_DEVICE_CONFIG,
+                Manifest.permission.READ_DEVICE_CONFIG);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
+                Integer.toString(3), /*makeDefault*/false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
+                Integer.toString(1000), /*makeDefault*/false);
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+        // Raise 2 failures at t=0 and t=900 respectively
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+        moveTimeForwardAndDispatch(900);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // Raise 2 failures at t=1100
+        moveTimeForwardAndDispatch(200);
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)));
+        mTestLooper.dispatchAll();
+
+        // We should receive APP_A since there are 3 failures within 1000ms window
+        assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
     private void adoptShellPermissions(String... permissions) {
@@ -772,6 +863,21 @@
         }
     }
 
+    private void moveTimeForwardAndDispatch(long milliSeconds) {
+        mTestClock.moveTimeForward(milliSeconds);
+        mTestLooper.moveTimeForward(milliSeconds);
+        mTestLooper.dispatchAll();
+    }
+
+    /** Trigger package failures above the threshold. */
+    private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
+            List<VersionedPackage> packages) {
+        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+            watchdog.onPackageFailure(packages);
+        }
+        mTestLooper.dispatchAll();
+    }
+
     private PackageWatchdog createWatchdog() {
         return createWatchdog(new TestController(), true /* withPackagesReady */);
     }
@@ -782,15 +888,15 @@
         Handler handler = new Handler(mTestLooper.getLooper());
         PackageWatchdog watchdog =
                 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
-                        mConnectivityModuleConnector);
+                        mConnectivityModuleConnector, mTestClock);
         // Verify controller is not automatically started
-        assertFalse(controller.mIsEnabled);
+        assertThat(controller.mIsEnabled).isFalse();
         if (withPackagesReady) {
             // Only capture the NetworkStack callback for the latest registered watchdog
             reset(mConnectivityModuleConnector);
             watchdog.onPackagesReady();
             // Verify controller by default is started when packages are ready
-            assertTrue(controller.mIsEnabled);
+            assertThat(controller.mIsEnabled).isTrue();
 
             verify(mConnectivityModuleConnector).registerHealthListener(
                     mConnectivityModuleCallbackCaptor.capture());
@@ -801,7 +907,8 @@
     private static class TestObserver implements PackageHealthObserver {
         private final String mName;
         private int mImpact;
-        final List<String> mFailedPackages = new ArrayList<>();
+        final List<String> mHealthCheckFailedPackages = new ArrayList<>();
+        final List<String> mMitigatedPackages = new ArrayList<>();
 
         TestObserver(String name) {
             mName = name;
@@ -814,11 +921,12 @@
         }
 
         public int onHealthCheckFailed(VersionedPackage versionedPackage) {
+            mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
             return mImpact;
         }
 
         public boolean execute(VersionedPackage versionedPackage) {
-            mFailedPackages.add(versionedPackage.getPackageName());
+            mMitigatedPackages.add(versionedPackage.getPackageName());
             return true;
         }
 
@@ -888,4 +996,17 @@
             }
         }
     }
+
+    private static class TestClock implements PackageWatchdog.SystemClock {
+        // Note 0 is special to the internal clock of PackageWatchdog. We need to start from
+        // a non-zero value in order not to disrupt the logic of PackageWatchdog.
+        private long mUpTimeMillis = 1;
+        @Override
+        public long uptimeMillis() {
+            return mUpTimeMillis;
+        }
+        public void moveTimeForward(long milliSeconds) {
+            mUpTimeMillis += milliSeconds;
+        }
+    }
 }
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bd5931..231d045b 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -31,9 +31,9 @@
 }
 
 java_test_host {
-    name: "SecondaryUserRollbackTest",
-    srcs: ["SecondaryUserRollbackTest/src/**/*.java"],
+    name: "MultiUserRollbackTest",
+    srcs: ["MultiUserRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
     test_suites: ["general-tests"],
-    test_config: "SecondaryUserRollbackTest.xml",
+    test_config: "MultiUserRollbackTest.xml",
 }
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest.xml b/tests/RollbackTest/MultiUserRollbackTest.xml
similarity index 67%
rename from tests/RollbackTest/SecondaryUserRollbackTest.xml
rename to tests/RollbackTest/MultiUserRollbackTest.xml
index 6b3f05c..41cec46 100644
--- a/tests/RollbackTest/SecondaryUserRollbackTest.xml
+++ b/tests/RollbackTest/MultiUserRollbackTest.xml
@@ -13,17 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Runs the rollback test from a secondary user">
-    <option name="test-suite-tag" value="SecondaryUserRollbackTest" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="RollbackTest.apk" />
-    </target_preparer>
+<configuration description="Runs rollback tests for multiple users">
+    <option name="test-suite-tag" value="MultiUserRollbackTest" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
-        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class" value="com.android.tests.rollback.host.SecondaryUserRollbackTest" />
+        <option name="class" value="com.android.tests.rollback.host.MultiUserRollbackTest" />
     </test>
 </configuration>
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
new file mode 100644
index 0000000..52f6eba
--- /dev/null
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.rollback.host;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs rollback tests for multiple users.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MultiUserRollbackTest extends BaseHostJUnit4Test {
+    // The user that was running originally when the test starts.
+    private int mOriginalUserId;
+    private int mSecondaryUserId = -1;
+    private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
+    private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
+
+
+    @After
+    public void tearDown() throws Exception {
+        getDevice().switchUser(mOriginalUserId);
+        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
+        removeSecondaryUserIfNecessary();
+    }
+
+    @Before
+    public void setup() throws Exception {
+        mOriginalUserId = getDevice().getCurrentUser();
+        installPackageAsUser("RollbackTest.apk", true, mOriginalUserId);
+        createAndSwitchToSecondaryUserIfNecessary();
+        installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId);
+    }
+
+    @Test
+    public void testBasicForSecondaryUser() throws Exception {
+        runPhaseForUsers("testBasic", mSecondaryUserId);
+    }
+
+    @Test
+    public void testMultipleUsers() throws Exception {
+        runPhaseForUsers("testMultipleUsersInstallV1", mOriginalUserId, mSecondaryUserId);
+        runPhaseForUsers("testMultipleUsersUpgradeToV2", mOriginalUserId);
+        runPhaseForUsers("testMultipleUsersUpdateUserData", mOriginalUserId, mSecondaryUserId);
+        switchToUser(mOriginalUserId);
+        getDevice().executeShellCommand("pm rollback-app com.android.cts.install.lib.testapp.A");
+        runPhaseForUsers("testMultipleUsersVerifyUserdataRollback", mOriginalUserId,
+                mSecondaryUserId);
+    }
+
+    /**
+     * Run the phase for the given user ids, in the order they are given.
+     */
+    private void runPhaseForUsers(String phase, int... userIds) throws Exception {
+        for (int userId: userIds) {
+            switchToUser(userId);
+            assertTrue(runDeviceTests("com.android.tests.rollback",
+                    "com.android.tests.rollback.MultiUserRollbackTest",
+                    phase));
+        }
+    }
+
+    private void removeSecondaryUserIfNecessary() throws Exception {
+        if (mSecondaryUserId != -1) {
+            getDevice().removeUser(mSecondaryUserId);
+            mSecondaryUserId = -1;
+        }
+    }
+
+    private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
+        if (mSecondaryUserId == -1) {
+            mOriginalUserId = getDevice().getCurrentUser();
+            mSecondaryUserId = getDevice().createUser("MultiUserRollbackTest_User"
+                    + System.currentTimeMillis());
+            switchToUser(mSecondaryUserId);
+        }
+    }
+
+    private void switchToUser(int userId) throws Exception {
+        if (getDevice().getCurrentUser() == userId) {
+            return;
+        }
+
+        assertTrue(getDevice().switchUser(userId));
+        for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
+            String userState = getDevice().executeShellCommand("am get-started-user-state "
+                    + userId);
+            if (userState.contains("RUNNING_UNLOCKED")) {
+                return;
+            }
+            Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
+        }
+        fail("User switch to user " + userId + " timed out");
+    }
+}
diff --git a/tests/RollbackTest/RollbackTest.xml b/tests/RollbackTest/RollbackTest.xml
index 70cd867..a14b01c 100644
--- a/tests/RollbackTest/RollbackTest.xml
+++ b/tests/RollbackTest/RollbackTest.xml
@@ -22,8 +22,9 @@
         <option name="package" value="com.android.tests.rollback" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
 
-        <!-- Exclude the StagedRollbackTest tests, which needs to be specially
-             driven from the StagedRollbackTest host test -->
+        <!-- Exclude the StagedRollbackTest and MultiUserRollbackTest tests, which need to be
+             specially driven from the StagedRollbackTest and MultiUserRollbackTest host test -->
         <option name="exclude-filter" value="com.android.tests.rollback.StagedRollbackTest" />
+        <option name="exclude-filter" value="com.android.tests.rollback.MultiUserRollbackTest" />
     </test>
 </configuration>
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
new file mode 100644
index 0000000..0ffe041
--- /dev/null
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/MultiUserRollbackTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.rollback;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+@RunWith(JUnit4.class)
+public class MultiUserRollbackTest {
+
+    @Before
+    public void adoptShellPermissions() {
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES,
+                Manifest.permission.TEST_MANAGE_ROLLBACKS,
+                Manifest.permission.MANAGE_ROLLBACKS);
+    }
+
+    @After
+    public void dropShellPermissions() {
+        InstallUtils.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        new RollbackTest().testBasic();
+    }
+
+    /**
+     * Install version 1 of the test app. This method is run for both users.
+     */
+    @Test
+    public void testMultipleUsersInstallV1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
+    /**
+     * Upgrade the test app to version 2. This method should only run once as the system user,
+     * and will update the app for both users.
+     */
+    @Test
+    public void testMultipleUsersUpgradeToV2() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+                rm.getAvailableRollbacks(), TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+    }
+
+    /**
+     * This method is run for both users. Assert that the test app has upgraded for both users, and
+     * update their userdata to reflect this new version.
+     */
+    @Test
+    public void testMultipleUsersUpdateUserData() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
+    /**
+     * The system will have rolled back the test app at this stage. Verify that the rollback has
+     * taken place, and that the userdata has been correctly rolled back. This method is run for
+     * both users.
+     */
+    @Test
+    public void testMultipleUsersVerifyUserdataRollback() {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+}
diff --git a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java b/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
deleted file mode 100644
index 11a0fbb..0000000
--- a/tests/RollbackTest/SecondaryUserRollbackTest/src/com/android/tests/rollback/host/SecondaryUserRollbackTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.rollback.host;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Runs rollback tests from a secondary user.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class SecondaryUserRollbackTest extends BaseHostJUnit4Test {
-    private static final int SYSTEM_USER_ID = 0;
-    // The user that was running originally when the test starts.
-    private int mOriginalUser = SYSTEM_USER_ID;
-    private int mSecondaryUserId = -1;
-    private static final long SWITCH_USER_COMPLETED_NUMBER_OF_POLLS = 60;
-    private static final long SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS = 1000;
-
-
-    @After
-    public void tearDown() throws Exception {
-        getDevice().switchUser(mOriginalUser);
-        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
-        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
-        removeSecondaryUserIfNecessary();
-    }
-
-    @Before
-    public void setup() throws Exception {
-        createAndSwitchToSecondaryUserIfNecessary();
-        installPackageAsUser("RollbackTest.apk", true, mSecondaryUserId, "--user current");
-    }
-
-    @Test
-    public void testBasic() throws Exception {
-        assertTrue(runDeviceTests("com.android.tests.rollback",
-                "com.android.tests.rollback.RollbackTest",
-                "testBasic"));
-    }
-
-    private void removeSecondaryUserIfNecessary() throws Exception {
-        if (mSecondaryUserId != -1) {
-            getDevice().removeUser(mSecondaryUserId);
-            mSecondaryUserId = -1;
-        }
-    }
-
-    private void createAndSwitchToSecondaryUserIfNecessary() throws Exception {
-        if (mSecondaryUserId == -1) {
-            mOriginalUser = getDevice().getCurrentUser();
-            mSecondaryUserId = getDevice().createUser("SecondaryUserRollbackTest_User");
-            assertTrue(getDevice().switchUser(mSecondaryUserId));
-            // give time for user to be switched
-            waitForSwitchUserCompleted(mSecondaryUserId);
-        }
-    }
-
-    private void waitForSwitchUserCompleted(int userId) throws Exception {
-        for (int i = 0; i < SWITCH_USER_COMPLETED_NUMBER_OF_POLLS; ++i) {
-            String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
-                    "ActivityManager:D");
-            if (logs.contains("Posting BOOT_COMPLETED user #" + userId)) {
-                return;
-            }
-            Thread.sleep(SWITCH_USER_COMPLETED_POLL_INTERVAL_IN_MILLIS);
-        }
-        fail("User switch to user " + userId + " timed out");
-    }
-}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index b2e5a62..bc98f06 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -28,6 +28,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Runs the staged rollback tests.
  */
@@ -99,9 +101,15 @@
         // crash system_server enough times to trigger a rollback
         crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
 
-        // Rollback should be committed automatically now
-        // Give time for rollback to be committed
-        assertTrue(getDevice().waitForDeviceNotAvailable(60000));
+        // Rollback should be committed automatically now.
+        // Give time for rollback to be committed. This could take a while,
+        // because we need all of the following to happen:
+        // 1. system_server comes back up and boot completes.
+        // 2. Rollback health observer detects updatable crashing signal.
+        // 3. Staged rollback session becomes ready.
+        // 4. Device actually reboots.
+        // So we give a generous timeout here.
+        assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
         getDevice().waitForDeviceAvailable();
 
         // verify rollback committed
diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING
index 7ae03e6..fefde5b 100644
--- a/tests/RollbackTest/TEST_MAPPING
+++ b/tests/RollbackTest/TEST_MAPPING
@@ -7,7 +7,7 @@
       "name": "StagedRollbackTest"
     },
     {
-      "name": "SecondaryUserRollbackTest"
+      "name": "MultiUserRollbackTest"
     }
   ]
 }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index 502aa97..e91abb6 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -20,8 +20,6 @@
         "libdl_android",
         "libhidl-gen-utils",
         "libhidlbase",
-        "libhidltransport",
-        "libhwbinder",
         "libjsoncpp",
         "liblog",
         "liblzma",
diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp
index 16a68d7..7d9b7b7 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -14,6 +14,39 @@
 // limitations under the License.
 //
 
+android_test {
+    name: "FrameworksNetIntegrationTests",
+    platform_apis: true,
+    certificate: "platform",
+    srcs: [
+        "src/**/*.kt",
+        "src/**/*.aidl",
+    ],
+    libs: [
+        "android.test.mock",
+    ],
+    static_libs: [
+        "TestNetworkStackLib",
+        "androidx.test.ext.junit",
+        "frameworks-net-integration-testutils",
+        "kotlin-reflect",
+        "mockito-target-extended-minus-junit4",
+        "net-tests-utils",
+        "services.core",
+        "services.net",
+        "testables",
+    ],
+    use_embedded_native_libs: true,
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+        // android_library does not include JNI libs: include NetworkStack dependencies here
+        "libnativehelper_compat_libc++",
+        "libnetworkstackutilsjni",
+    ],
+}
+
 // Utilities for testing framework code both in integration and unit tests.
 java_library {
     name: "frameworks-net-integration-testutils",
diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml
new file mode 100644
index 0000000..91b3cd9
--- /dev/null
+++ b/tests/net/integration/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.server.net.integrationtests">
+
+    <!-- For ConnectivityService registerReceiverAsUser (receiving broadcasts) -->
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <!-- PermissionMonitor sets network permissions for each user -->
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <!-- ConnectivityService sends notifications to BatteryStats -->
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+
+        <!-- This manifest is merged with the base manifest of the real NetworkStack app.
+             Remove the NetworkStackService from the base (real) manifest, and replace with a test
+             service that responds to the same intent -->
+        <service android:name="com.android.server.NetworkStackService" tools:node="remove"/>
+        <service android:name=".TestNetworkStackService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack">
+            <intent-filter>
+                <action android:name="android.net.INetworkStackConnector.Test"/>
+            </intent-filter>
+        </service>
+        <service android:name=".NetworkStackInstrumentationService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack">
+            <intent-filter>
+                <action android:name=".INetworkStackInstrumentation"/>
+            </intent-filter>
+        </service>
+        <service tools:replace="android:process"
+                 android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+                 android:process="com.android.server.net.integrationtests.testnetworkstack"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.net.integrationtests"
+                     android:label="Frameworks Net Integration Tests" />
+
+</manifest>
diff --git a/tests/net/integration/res/values/config.xml b/tests/net/integration/res/values/config.xml
new file mode 100644
index 0000000..2c8046f
--- /dev/null
+++ b/tests/net/integration/res/values/config.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!--
+    Override configuration for testing. The below settings use the config_ variants, which are
+    normally used by RROs to override the setting with highest priority. -->
+    <integer name="config_captive_portal_dns_probe_timeout">12500</integer>
+    <string name="config_captive_portal_http_url" translatable="false">http://test.android.com</string>
+    <string name="config_captive_portal_https_url" translatable="false">https://secure.test.android.com</string>
+    <string-array name="config_captive_portal_fallback_urls" translatable="false">
+        <item>http://fallback1.android.com</item>
+        <item>http://fallback2.android.com</item>
+    </string-array>
+    <string-array name="config_captive_portal_fallback_probe_specs" translatable="false">
+    </string-array>
+</resources>
diff --git a/tests/net/integration/src/android/net/TestNetworkStackClient.kt b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
new file mode 100644
index 0000000..01eb514
--- /dev/null
+++ b/tests/net/integration/src/android/net/TestNetworkStackClient.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.IBinder
+import com.android.server.net.integrationtests.TestNetworkStackService
+import org.mockito.Mockito.any
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import kotlin.test.fail
+
+const val TEST_ACTION_SUFFIX = ".Test"
+
+class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+    // TODO: consider switching to TrackRecord for more expressive checks
+    private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+
+    private class TestDependencies(private val context: Context) : Dependencies {
+        override fun addToServiceManager(service: IBinder) = Unit
+        override fun checkCallerUid() = Unit
+
+        override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
+            return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
+                getNetworkStackIntent(inSystemProcess)
+            }.also { it.init(context) }
+        }
+
+        private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
+            // Simulate out-of-system-process config: in-process service not found (null intent)
+            if (inSystemProcess) return null
+            val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
+            val serviceName = TestNetworkStackService::class.qualifiedName
+                    ?: fail("TestNetworkStackService name not found")
+            intent.component = ComponentName(context.packageName, serviceName)
+            return intent
+        }
+    }
+
+    // base may be an instance of an inaccessible subclass, so non-spyable.
+    // Use a known open class that delegates to the original instance for all methods except
+    // asBinder. asBinder needs to use its own non-delegated implementation as otherwise it would
+    // return a binder token to a class that is not spied on.
+    open class NetworkMonitorCallbacksWrapper(private val base: INetworkMonitorCallbacks) :
+            INetworkMonitorCallbacks.Stub(), INetworkMonitorCallbacks by base {
+        // asBinder is implemented by both base class and delegate: specify explicitly
+        override fun asBinder(): IBinder {
+            return super.asBinder()
+        }
+    }
+
+    override fun makeNetworkMonitor(network: Network, name: String?, cb: INetworkMonitorCallbacks) {
+        val cbSpy = spy(NetworkMonitorCallbacksWrapper(cb))
+        lastCallbacks[network] = cbSpy
+        super.makeNetworkMonitor(network, name, cbSpy)
+    }
+
+    fun verifyNetworkMonitorCreated(network: Network, timeoutMs: Long) {
+        val cb = lastCallbacks[network]
+                ?: fail("NetworkMonitor for network $network not requested")
+        verify(cb, timeout(timeoutMs)).onNetworkMonitorCreated(any())
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
new file mode 100644
index 0000000..334b26d
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Context.BIND_IMPORTANT
+import android.content.Intent
+import android.content.ServiceConnection
+import android.net.ConnectivityManager
+import android.net.IDnsResolver
+import android.net.INetd
+import android.net.INetworkPolicyManager
+import android.net.INetworkStatsService
+import android.net.LinkProperties
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkRequest
+import android.net.TestNetworkStackClient
+import android.net.metrics.IpConnectivityLog
+import android.os.ConditionVariable
+import android.os.IBinder
+import android.os.INetworkManagementService
+import android.testing.TestableContext
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.ConnectivityService
+import com.android.server.LocalServices
+import com.android.server.NetworkAgentWrapper
+import com.android.server.TestNetIdManager
+import com.android.server.connectivity.DefaultNetworkMetrics
+import com.android.server.connectivity.IpConnectivityMetrics
+import com.android.server.connectivity.MockableSystemProperties
+import com.android.server.connectivity.ProxyTracker
+import com.android.server.connectivity.Tethering
+import com.android.server.net.NetworkPolicyManagerInternal
+import com.android.testutils.TestableNetworkCallback
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.Spy
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+const val SERVICE_BIND_TIMEOUT_MS = 5_000L
+const val TEST_TIMEOUT_MS = 1_000L
+
+/**
+ * Test that exercises an instrumented version of ConnectivityService against an instrumented
+ * NetworkStack in a different test process.
+ */
+@RunWith(AndroidJUnit4::class)
+class ConnectivityServiceIntegrationTest {
+    // lateinit used here for mocks as they need to be reinitialized between each test and the test
+    // should crash if they are used before being initialized.
+    @Mock
+    private lateinit var netManager: INetworkManagementService
+    @Mock
+    private lateinit var statsService: INetworkStatsService
+    @Mock
+    private lateinit var policyManager: INetworkPolicyManager
+    @Mock
+    private lateinit var log: IpConnectivityLog
+    @Mock
+    private lateinit var netd: INetd
+    @Mock
+    private lateinit var dnsResolver: IDnsResolver
+    @Mock
+    private lateinit var metricsLogger: IpConnectivityMetrics.Logger
+    @Mock
+    private lateinit var defaultMetrics: DefaultNetworkMetrics
+    @Spy
+    private var context = TestableContext(realContext)
+
+    // lateinit for these three classes under test, as they should be reset to a different instance
+    // for every test but should always be initialized before use (or the test should crash).
+    private lateinit var networkStackClient: TestNetworkStackClient
+    private lateinit var service: ConnectivityService
+    private lateinit var cm: ConnectivityManager
+
+    companion object {
+        // lateinit for this binder token, as it must be initialized before any test code is run
+        // and use of it before init should crash the test.
+        private lateinit var nsInstrumentation: INetworkStackInstrumentation
+        private val bindingCondition = ConditionVariable(false)
+
+        private val realContext get() = InstrumentationRegistry.getInstrumentation().context
+
+        private class InstrumentationServiceConnection : ServiceConnection {
+            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+                Log.i("TestNetworkStack", "Service connected")
+                try {
+                    if (service == null) fail("Error binding to NetworkStack instrumentation")
+                    if (::nsInstrumentation.isInitialized) fail("Service already connected")
+                    nsInstrumentation = INetworkStackInstrumentation.Stub.asInterface(service)
+                } finally {
+                    bindingCondition.open()
+                }
+            }
+
+            override fun onServiceDisconnected(name: ComponentName?) = Unit
+        }
+
+        @BeforeClass
+        @JvmStatic
+        fun setUpClass() {
+            val intent = Intent(realContext, NetworkStackInstrumentationService::class.java)
+            intent.action = INetworkStackInstrumentation::class.qualifiedName
+            assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(),
+                    BIND_AUTO_CREATE or BIND_IMPORTANT),
+                    "Error binding to instrumentation service")
+            assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS),
+                    "Timed out binding to instrumentation service " +
+                            "after $SERVICE_BIND_TIMEOUT_MS ms")
+        }
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        doReturn(defaultMetrics).`when`(metricsLogger).defaultNetworkMetrics()
+        doNothing().`when`(context).sendStickyBroadcastAsUser(any(), any(), any())
+
+        networkStackClient = TestNetworkStackClient(realContext)
+        networkStackClient.init()
+        networkStackClient.start()
+
+        LocalServices.removeServiceForTest(NetworkPolicyManagerInternal::class.java)
+        LocalServices.addService(NetworkPolicyManagerInternal::class.java,
+                mock(NetworkPolicyManagerInternal::class.java))
+
+        service = TestConnectivityService(makeDependencies())
+        cm = ConnectivityManager(context, service)
+        context.addMockSystemService(Context.CONNECTIVITY_SERVICE, cm)
+
+        service.systemReady()
+    }
+
+    private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
+            context, netManager, statsService, policyManager, dnsResolver, log, netd, deps)
+
+    private fun makeDependencies(): ConnectivityService.Dependencies {
+        val deps = spy(ConnectivityService.Dependencies())
+        doReturn(networkStackClient).`when`(deps).networkStack
+        doReturn(metricsLogger).`when`(deps).metricsLogger
+        doReturn(mock(Tethering::class.java)).`when`(deps).makeTethering(
+                any(), any(), any(), any(), any())
+        doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
+        doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
+        doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
+        return deps
+    }
+
+    @After
+    fun tearDown() {
+        nsInstrumentation.clearAllState()
+    }
+
+    @Test
+    fun testValidation() {
+        val request = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .build()
+        val testCallback = TestableNetworkCallback()
+
+        cm.registerNetworkCallback(request, testCallback)
+        nsInstrumentation.addHttpResponse(HttpResponse(
+                "http://test.android.com",
+                responseCode = 204, contentLength = 42, redirectUrl = null))
+        nsInstrumentation.addHttpResponse(HttpResponse(
+                "https://secure.test.android.com",
+                responseCode = 204, contentLength = 42, redirectUrl = null))
+
+        val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
+        networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
+
+        na.addCapability(NET_CAPABILITY_INTERNET)
+        na.connect()
+
+        testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
+        assertEquals(2, nsInstrumentation.getRequestUrls().size)
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/view/Transaction.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
similarity index 84%
copy from core/java/android/view/Transaction.aidl
copy to tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
index 8d24b43..9a2bcfe 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.aidl
@@ -11,9 +11,9 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.view.SurfaceControl;
+package com.android.server.net.integrationtests;
 
-parcelable Transaction;
+parcelable HttpResponse;
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
new file mode 100644
index 0000000..45073d8
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.os.Parcel
+import android.os.Parcelable
+
+data class HttpResponse(
+    val requestUrl: String,
+    val responseCode: Int,
+    val contentLength: Long,
+    val redirectUrl: String?
+) : Parcelable {
+    constructor(p: Parcel): this(p.readString(), p.readInt(), p.readLong(), p.readString())
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        with(dest) {
+            writeString(requestUrl)
+            writeInt(responseCode)
+            writeLong(contentLength)
+            writeString(redirectUrl)
+        }
+    }
+
+    override fun describeContents() = 0
+    companion object CREATOR : Parcelable.Creator<HttpResponse> {
+        override fun createFromParcel(source: Parcel) = HttpResponse(source)
+        override fun newArray(size: Int) = arrayOfNulls<HttpResponse?>(size)
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/view/Transaction.aidl b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
similarity index 65%
copy from core/java/android/view/Transaction.aidl
copy to tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
index 8d24b43..efc58ad 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/INetworkStackInstrumentation.aidl
@@ -11,9 +11,15 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
-package android.view.SurfaceControl;
+package com.android.server.net.integrationtests;
 
-parcelable Transaction;
+import com.android.server.net.integrationtests.HttpResponse;
+
+interface INetworkStackInstrumentation {
+    void clearAllState();
+    void addHttpResponse(in HttpResponse response);
+    List<String> getRequestUrls();
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
new file mode 100644
index 0000000..4827d29
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.app.Service
+import android.content.Intent
+import java.net.URL
+import java.util.Collections
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentLinkedQueue
+import kotlin.collections.ArrayList
+import kotlin.test.fail
+
+/**
+ * An instrumentation interface for the NetworkStack that allows controlling behavior to
+ * facilitate integration tests.
+ */
+class NetworkStackInstrumentationService : Service() {
+    override fun onBind(intent: Intent) = InstrumentationConnector.asBinder()
+
+    object InstrumentationConnector : INetworkStackInstrumentation.Stub() {
+        private val httpResponses = ConcurrentHashMap<String, ConcurrentLinkedQueue<HttpResponse>>()
+                .run {
+                    withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } }
+                }
+        private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>())
+
+        /**
+         * Called when an HTTP request is being processed by NetworkMonitor. Returns the response
+         * that should be simulated.
+         */
+        fun processRequest(url: URL): HttpResponse {
+            val strUrl = url.toString()
+            httpRequestUrls.add(strUrl)
+            return httpResponses[strUrl]?.poll()
+                    ?: fail("No mocked response for request: $strUrl. " +
+                            "Mocked URL keys are: ${httpResponses.keys}")
+        }
+
+        /**
+         * Clear all state of this connector. This is intended for use between two tests, so all
+         * state should be reset as if the connector was just created.
+         */
+        override fun clearAllState() {
+            httpResponses.clear()
+            httpRequestUrls.clear()
+        }
+
+        /**
+         * Add a response to a future HTTP request.
+         *
+         * <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
+         * used to mock the query response.
+         */
+        override fun addHttpResponse(response: HttpResponse) {
+            httpResponses.getValue(response.requestUrl).add(response)
+        }
+
+        /**
+         * Get the ordered list of request URLs that have been sent by NetworkMonitor, and were
+         * answered based on mock responses.
+         */
+        override fun getRequestUrls(): List<String> {
+            return ArrayList(httpRequestUrls)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
new file mode 100644
index 0000000..8e4a9dd
--- /dev/null
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.net.integrationtests
+
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.net.INetworkMonitorCallbacks
+import android.net.Network
+import android.net.metrics.IpConnectivityLog
+import android.net.util.SharedLog
+import android.os.IBinder
+import com.android.networkstack.metrics.DataStallStatsUtils
+import com.android.server.NetworkStackService.NetworkMonitorConnector
+import com.android.server.NetworkStackService.NetworkStackConnector
+import com.android.server.connectivity.NetworkMonitor
+import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import java.net.HttpURLConnection
+import java.net.URL
+import java.net.URLConnection
+
+private const val TEST_NETID = 42
+
+/**
+ * Android service that can return an [android.net.INetworkStackConnector] which can be instrumented
+ * through [NetworkStackInstrumentationService].
+ * Useful in tests to create test instrumented NetworkStack components that can receive
+ * instrumentation commands through [NetworkStackInstrumentationService].
+ */
+class TestNetworkStackService : Service() {
+    override fun onBind(intent: Intent): IBinder = TestNetworkStackConnector(makeTestContext())
+
+    private fun makeTestContext() = spy(applicationContext).also {
+        doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
+    }
+
+    private class TestPermissionChecker : NetworkStackConnector.PermissionChecker() {
+        override fun enforceNetworkStackCallingPermission() = Unit
+    }
+
+    private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
+            NetworkMonitor.Dependencies() {
+        override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+        override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
+    }
+
+    private inner class TestNetworkStackConnector(context: Context) :
+            NetworkStackConnector(context, TestPermissionChecker()) {
+
+        private val network = Network(TEST_NETID)
+        private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
+
+        private inner class TestNetwork(netId: Int) : Network(netId) {
+            override fun openConnection(url: URL): URLConnection {
+                val response = InstrumentationConnector.processRequest(url)
+
+                val connection = mock(HttpURLConnection::class.java)
+                doReturn(response.responseCode).`when`(connection).responseCode
+                doReturn(response.contentLength).`when`(connection).contentLengthLong
+                doReturn(response.redirectUrl).`when`(connection).getHeaderField("location")
+                return connection
+            }
+        }
+
+        override fun makeNetworkMonitor(
+            network: Network,
+            name: String?,
+            cb: INetworkMonitorCallbacks
+        ) {
+            val nm = NetworkMonitor(this@TestNetworkStackService, cb,
+                    this.network,
+                    mock(IpConnectivityLog::class.java), mock(SharedLog::class.java),
+                    NetworkMonitorDeps(privateDnsBypassNetwork),
+                    mock(DataStallStatsUtils::class.java))
+            cb.onNetworkMonitorCreated(NetworkMonitorConnector(nm, TestPermissionChecker()))
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6ae639a..f2f258a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -184,6 +184,7 @@
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -2259,9 +2260,8 @@
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY));
@@ -2281,9 +2281,8 @@
         // If user accepted partial connectivity network before,
         // NetworkMonitor#setAcceptPartialConnectivity() will be called in
         // ConnectivityService#updateNetworkInfo().
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
@@ -2306,9 +2305,8 @@
         // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
         // notifyNetworkConnected.
         mWiFiNetworkAgent.connectWithPartialValidConnectivity();
-        waitForIdle();
-        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
         callback.expectCallback(CallbackRecord.LOSING, mCellNetworkAgent);
         callback.expectCapabilitiesWith(
                 NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -3633,6 +3631,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 140305589)
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -5697,6 +5696,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 140305678)
     public void testTcpBufferReset() throws Exception {
         final String testTcpBufferSizes = "1,2,3,4,5,6";
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
diff --git a/tests/net/java/com/android/server/NetIdManagerTest.kt b/tests/net/java/com/android/server/NetIdManagerTest.kt
new file mode 100644
index 0000000..045f89f
--- /dev/null
+++ b/tests/net/java/com/android/server/NetIdManagerTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.server.NetIdManager.MIN_NET_ID
+import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetIdManagerTest {
+    @Test
+    fun testReserveReleaseNetId() {
+        val manager = NetIdManager(MIN_NET_ID + 4)
+        assertEquals(MIN_NET_ID, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
+
+        manager.releaseNetId(MIN_NET_ID + 1)
+        manager.releaseNetId(MIN_NET_ID + 3)
+        // IDs only loop once there is no higher ID available
+        assertEquals(MIN_NET_ID + 4, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 1, manager.reserveNetId())
+        assertEquals(MIN_NET_ID + 3, manager.reserveNetId())
+        assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
+        manager.releaseNetId(MIN_NET_ID + 5)
+        // Still no ID available: MIN_NET_ID + 5 was not reserved
+        assertThrows(IllegalStateException::class.java, ThrowingRunnable { manager.reserveNetId() })
+        manager.releaseNetId(MIN_NET_ID + 2)
+        // Throwing an exception still leaves the manager in a working state
+        assertEquals(MIN_NET_ID + 2, manager.reserveNetId())
+    }
+}
\ No newline at end of file
diff --git a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
index 273943f..4172743 100644
--- a/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
@@ -16,12 +16,12 @@
 
 package com.android.framework.permission.tests;
 
-import java.util.ArrayList;
-
 import android.telephony.SmsManager;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+
 /**
  * Verify that SmsManager apis cannot be called without required permissions.
  */
@@ -32,6 +32,10 @@
     private static final String DEST_NUMBER = "4567";
     private static final String SRC_NUMBER = "1234";
 
+    private static final int CELL_BROADCAST_MESSAGE_ID_START = 10;
+    private static final int CELL_BROADCAST_MESSAGE_ID_END = 20;
+    private static final int CELL_BROADCAST_GSM_RAN_TYPE = 0;
+
     /**
      * Verify that SmsManager.sendTextMessage requires permissions.
      * <p>Tests Permission:
@@ -82,4 +86,34 @@
             // expected
         }
     }
+
+    /**
+     * Verify that SmsManager.enableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testEnableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().enableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Verify that SmsManager.disableCellBroadcastRange requires permissions.
+     * <p>Tests system permission: android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST
+     */
+    @SmallTest
+    public void testDisableCellBroadcastRange() {
+        try {
+            SmsManager.getDefault().disableCellBroadcastRange(CELL_BROADCAST_MESSAGE_ID_START,
+                    CELL_BROADCAST_MESSAGE_ID_END, CELL_BROADCAST_GSM_RAN_TYPE);
+            fail("SmsManager.sendDataMessage did not throw SecurityException as expected");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
 }
diff --git a/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java b/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
index 1b8e58c..7057a90 100644
--- a/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
+++ b/tests/testables/src/android/testing/DexmakerShareClassLoaderRule.java
@@ -20,6 +20,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import libcore.util.SneakyThrow;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -55,7 +57,11 @@
      * WARNING: This is absolutely incompatible with running tests in parallel!
      */
     public static void runWithDexmakerShareClassLoader(Runnable r) {
-        apply(r::run).run();
+        try {
+            apply(r::run).run();
+        } catch (Throwable t) {
+            SneakyThrow.sneakyThrow(t);
+        }
     }
 
     /**
diff --git a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
index c83dfa4..e05fdce 100644
--- a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
+++ b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java
@@ -31,7 +31,7 @@
     }
 
     @Override
-    public void close() throws Exception {
+    public void close() {
         ViewRootImpl.sNewInsetsMode = mOldMode;
     }
 }
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 7ffa5ff..137fbd6 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -246,6 +246,36 @@
   Printer* printer_;
 };
 
+std::string OverlayablePoliciesToString(OverlayableItem::PolicyFlags policies) {
+  static const std::map<OverlayableItem::PolicyFlags, std::string> kFlagToString = {
+    {OverlayableItem::kPublic, "public"},
+    {OverlayableItem::kSystem, "system"},
+    {OverlayableItem::kVendor, "vendor"},
+    {OverlayableItem::kProduct, "product"},
+    {OverlayableItem::kSignature, "signature"},
+    {OverlayableItem::kOdm, "odm"},
+    {OverlayableItem::kOem, "oem"},
+  };
+  std::string str;
+  for (auto const& policy : kFlagToString) {
+    if ((policies & policy.first) != policy.first) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.second);
+    policies &= ~policy.first;
+  }
+  if (policies != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", policies));
+  }
+  return !str.empty() ? str : "none";
+}
+
 }  // namespace
 
 void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -312,6 +342,10 @@
             break;
         }
 
+        if (entry->overlayable_item) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
         if (options.show_values) {
@@ -525,4 +559,62 @@
   doc.root->Accept(&xml_visitor);
 }
 
+struct DumpOverlayableEntry {
+  std::string overlayable_section;
+  std::string policy_subsection;
+  std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+  std::vector<DumpOverlayableEntry> items;
+  for (const auto& package : table.packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        if (entry->overlayable_item) {
+          const auto& overlayable_item = entry->overlayable_item.value();
+          const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+              overlayable_item.overlayable->name.c_str(),
+              overlayable_item.overlayable->actor.c_str());
+          const auto policy_subsection = StringPrintf(R"(policies="%s")",
+              OverlayablePoliciesToString(overlayable_item.policies).c_str());
+          const auto value =
+            StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+          items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+        }
+      }
+    }
+  }
+
+  std::sort(items.begin(), items.end(),
+      [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+        if (a.overlayable_section != b.overlayable_section) {
+          return a.overlayable_section < b.overlayable_section;
+        }
+        if (a.policy_subsection != b.policy_subsection) {
+          return a.policy_subsection < b.policy_subsection;
+        }
+        return a.resource_name < b.resource_name;
+      });
+
+  std::string last_overlayable_section;
+  std::string last_policy_subsection;
+  for (const auto& item : items) {
+    if (last_overlayable_section != item.overlayable_section) {
+      printer->Println(item.overlayable_section);
+      last_overlayable_section = item.overlayable_section;
+    }
+    if (last_policy_subsection != item.policy_subsection) {
+      printer->Indent();
+      printer->Println(item.policy_subsection);
+      last_policy_subsection = item.policy_subsection;
+      printer->Undent();
+    }
+    printer->Indent();
+    printer->Indent();
+    printer->Println(item.resource_name);
+    printer->Undent();
+    printer->Undent();
+  }
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197c..9443d60 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
   static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+  static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 213bdd2..7966ba2 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -177,6 +177,11 @@
   return main_command->Execute(args, &std::cerr);
 }
 
+// TODO(b/141312058) stop leaks
+extern "C" const char *__asan_default_options() {
+    return "detect_leaks=0";
+}
+
 int main(int argc, char** argv) {
 #ifdef _WIN32
   LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc);
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index c7ac438..d50b1de 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -740,7 +740,6 @@
   }
 
   std::unique_ptr<io::IFileCollection> file_collection;
-  std::unique_ptr<IArchiveWriter> archive_writer;
 
   // Collect the resources files to compile
   if (options_.res_dir && options_.res_zip) {
@@ -761,8 +760,6 @@
       context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
       return 1;
     }
-
-    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   } else if (options_.res_zip) {
     if (!args.empty()) {
       context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
@@ -777,8 +774,6 @@
       context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
       return 1;
     }
-
-    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   } else {
     auto collection = util::make_unique<io::FileCollection>();
 
@@ -791,7 +786,14 @@
     }
 
     file_collection = std::move(collection);
+  }
+
+  std::unique_ptr<IArchiveWriter> archive_writer;
+  file::FileType output_file_type = file::GetFileType(options_.output_path);
+  if (output_file_type == file::FileType::kDirectory) {
     archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
+  } else {
+    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
   }
 
   if (!archive_writer) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 429aff1..3982d12 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -394,6 +394,17 @@
   return 0;
 }
 
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+  ResourceTable* table = apk->GetResourceTable();
+  if (!table) {
+    GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+    return 1;
+  }
+
+  Debug::DumpOverlayable(*table, GetPrinter());
+  return 0;
+}
+
 const char DumpBadgerCommand::kBadgerData[2925] = {
     32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
     32,  32,  32,  32,  32,  32,  95,  46,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bc..cd51f7a 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@
   std::vector<std::string> files_;
 };
 
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+  explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+      : DumpApkCommand("overlayable", printer, diag) {
+    SetDescription("Print the <overlayable> resources of an APK.");
+  }
+
+  int Dump(LoadedApk* apk) override;
+};
+
 /** The default dump command. Performs no action because a subcommand is required. */
 class DumpCommand : public Command {
  public:
@@ -255,8 +265,8 @@
     AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+    AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
-    // TODO(b/120609160): Add aapt2 overlayable dump command
   }
 
   int Action(const std::vector<std::string>& args) override {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 5e06818..e36668e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -53,9 +53,9 @@
 using ::android::ResTable_config;
 using ::android::StringPiece;
 using ::android::base::ReadFileToString;
-using ::android::base::WriteStringToFile;
 using ::android::base::StringAppendF;
 using ::android::base::StringPrintf;
+using ::android::base::WriteStringToFile;
 
 namespace aapt {
 
@@ -300,29 +300,7 @@
   OptimizeContext* context_;
 };
 
-bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context,
-                                           OptimizeOptions* options) {
-  std::string contents;
-  if (!ReadFileToString(path, &contents, true)) {
-    context->GetDiagnostics()->Error(DiagMessage()
-                                     << "failed to parse whitelist from config file: " << path);
-    return false;
-  }
-  for (StringPiece resource_name : util::Tokenize(contents, ',')) {
-    options->table_flattener_options.whitelisted_resources.insert(
-        resource_name.to_string());
-  }
-  return true;
-}
-
-bool ExtractConfig(const std::string& path, OptimizeContext* context,
-                                    OptimizeOptions* options) {
-  std::string content;
-  if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
-    context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist");
-    return false;
-  }
-
+bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) {
   size_t line_no = 0;
   for (StringPiece line : util::Tokenize(content, '\n')) {
     line_no++;
@@ -351,15 +329,24 @@
     for (StringPiece directive : util::Tokenize(directives, ',')) {
       if (directive == "remove") {
         options->resources_blacklist.insert(resource_name.ToResourceName());
-      } else if (directive == "no_obfuscate") {
-        options->table_flattener_options.whitelisted_resources.insert(
-            resource_name.entry.to_string());
+      } else if (directive == "no_collapse" || directive == "no_obfuscate") {
+        options->table_flattener_options.name_collapse_exemptions.insert(
+            resource_name.ToResourceName());
       }
     }
   }
   return true;
 }
 
+bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) {
+  std::string content;
+  if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+    context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file");
+    return false;
+  }
+  return ParseConfig(content, context, options);
+}
+
 bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
                                 OptimizeOptions* out_options) {
   const xml::XmlResource* manifest = apk->GetManifest();
@@ -467,15 +454,6 @@
     }
   }
 
-  if (options_.table_flattener_options.collapse_key_stringpool) {
-    if (whitelist_path_) {
-      std::string& path = whitelist_path_.value();
-      if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) {
-        return 1;
-      }
-    }
-  }
-
   if (resources_config_path_) {
     std::string& path = resources_config_path_.value();
     if (!ExtractConfig(path, &context, &options_)) {
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 0be7dad..5070ccc 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -78,10 +78,6 @@
             "All the resources that would be unused on devices of the given densities will be \n"
             "removed from the APK.",
         &target_densities_);
-    AddOptionalFlag("--whitelist-path",
-        "Path to the whitelist.cfg file containing whitelisted resources \n"
-            "whose names should not be altered in final resource tables.",
-        &whitelist_path_);
     AddOptionalFlag("--resources-config-path",
         "Path to the resources.cfg file containing the list of resources and \n"
             "directives to each resource. \n"
@@ -104,11 +100,13 @@
         "Enables encoding sparse entries using a binary search tree.\n"
             "This decreases APK size at the cost of resource retrieval performance.",
         &options_.table_flattener_options.use_sparse_entries);
-    AddOptionalSwitch("--enable-resource-obfuscation",
-        "Enables obfuscation of key string pool to single value",
+    AddOptionalSwitch("--collapse-resource-names",
+        "Collapses resource names to a single value in the key string pool. Resources can \n"
+            "be exempted using the \"no_collapse\" directive in a file specified by "
+            "--resources-config-path.",
         &options_.table_flattener_options.collapse_key_stringpool);
-    AddOptionalSwitch("--enable-resource-path-shortening",
-        "Enables shortening of the path of the resources inside the APK.",
+    AddOptionalSwitch("--shorten-resource-paths",
+        "Shortens the paths of resources inside the APK.",
         &options_.shorten_resource_paths);
     AddOptionalFlag("--resource-path-shortening-map",
         "Path to output the map of old resource paths to shortened paths.",
@@ -125,7 +123,6 @@
                                const std::string &file_path);
 
   Maybe<std::string> config_path_;
-  Maybe<std::string> whitelist_path_;
   Maybe<std::string> resources_config_path_;
   Maybe<std::string> target_densities_;
   std::vector<std::string> configs_;
diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp
new file mode 100644
index 0000000..ac681e8
--- /dev/null
+++ b/tools/aapt2/cmd/Optimize_test.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Optimize.h"
+
+#include "AppInfo.h"
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Resource.h"
+#include "test/Test.h"
+
+using testing::Contains;
+using testing::Eq;
+
+namespace aapt {
+
+bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*);
+
+using OptimizeTest = CommandTestFixture;
+
+TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) {
+  const std::string& content = R"(
+string/foo#no_collapse
+dimen/bar#no_collapse
+)";
+  aapt::test::Context context;
+  OptimizeOptions options;
+  ParseConfig(content, &context, &options);
+
+  const std::set<ResourceName>& name_collapse_exemptions =
+      options.table_flattener_options.name_collapse_exemptions;
+
+  ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) {
+  const std::string& content = R"(
+string/foo#no_obfuscate
+dimen/bar#no_obfuscate
+)";
+  aapt::test::Context context;
+  OptimizeOptions options;
+  ParseConfig(content, &context, &options);
+
+  const std::set<ResourceName>& name_collapse_exemptions =
+      options.table_flattener_options.name_collapse_exemptions;
+
+  ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
+  EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index b932117..58e232c 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -228,14 +228,15 @@
  public:
   PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
                    const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
-                   bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
+                   bool collapse_key_stringpool,
+                   const std::set<ResourceName>& name_collapse_exemptions)
       : context_(context),
         diag_(context->GetDiagnostics()),
         package_(package),
         shared_libs_(shared_libs),
         use_sparse_entries_(use_sparse_entries),
         collapse_key_stringpool_(collapse_key_stringpool),
-        whitelisted_resources_(whitelisted_resources) {
+        name_collapse_exemptions_(name_collapse_exemptions) {
   }
 
   bool FlattenPackage(BigBuffer* buffer) {
@@ -652,11 +653,12 @@
 
       for (ResourceEntry* entry : sorted_entries) {
         uint32_t local_key_index;
+        ResourceName resource_name({}, type->type, entry->name);
         if (!collapse_key_stringpool_ ||
-            whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
+            name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
           local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
         } else {
-          // resource isn't whitelisted, add it as obfuscated value
+          // resource isn't exempt from collapse, add it as obfuscated value
           local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
         }
         // Group values by configuration.
@@ -712,7 +714,7 @@
   StringPool type_pool_;
   StringPool key_pool_;
   bool collapse_key_stringpool_;
-  const std::set<std::string>& whitelisted_resources_;
+  const std::set<ResourceName>& name_collapse_exemptions_;
 };
 
 }  // namespace
@@ -760,7 +762,7 @@
 
     PackageFlattener flattener(context, package.get(), &table->included_packages_,
                                options_.use_sparse_entries, options_.collapse_key_stringpool,
-                               options_.whitelisted_resources);
+                               options_.name_collapse_exemptions);
     if (!flattener.FlattenPackage(&package_buffer)) {
       return false;
     }
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 73c1729..4360db1 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -19,6 +19,7 @@
 
 #include "android-base/macros.h"
 
+#include "Resource.h"
 #include "ResourceTable.h"
 #include "process/IResourceTableConsumer.h"
 #include "util/BigBuffer.h"
@@ -41,8 +42,8 @@
   // have name indices that point to this single value
   bool collapse_key_stringpool = false;
 
-  // Set of whitelisted resource names to avoid altering in key stringpool
-  std::set<std::string> whitelisted_resources;
+  // Set of resources to avoid collapsing to a single entry in key stringpool.
+  std::set<ResourceName> name_collapse_exemptions;
 
   // Map from original resource paths to shortened resource paths.
   std::map<std::string, std::string> shortened_path_map;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index a940923..8fbdd7f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -518,7 +518,7 @@
   ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
 }
 
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
@@ -572,7 +572,7 @@
                      ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
 }
 
-TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) {
+TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .SetPackageId("com.app.test", 0x7f)
@@ -591,21 +591,22 @@
 
   TableFlattenerOptions options;
   options.collapse_key_stringpool = true;
-  options.whitelisted_resources.insert("test");
-  options.whitelisted_resources.insert("three");
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
+  options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
   ResTable res_table;
 
   ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
 
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
                      ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
   EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
                      ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
-                     Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
+                     ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
 
+  // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
   EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
                      ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
                      ResTable_config::CONFIG_VERSION));
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index f3be483..c3c16b9 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -83,6 +83,15 @@
     Attribute default_attribute(android::ResTable_map::TYPE_ANY);
     default_attribute.SetWeak(true);
 
+    // The default orientation of gradients in android Q is different than previous android
+    // versions. Set the android:angle attribute to "0" to ensure that the default gradient
+    // orientation will remain left-to-right in android Q.
+    if (el->name == "gradient" && context_->GetMinSdkVersion() <= SDK_Q) {
+      if (!el->FindAttribute(xml::kSchemaAndroid, "angle")) {
+        el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "angle", "0"});
+      }
+    }
+
     const Source source = source_.WithLine(el->line_number);
     for (xml::Attribute& attr : el->attributes) {
       // If the attribute has no namespace, interpret values as if
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index ef99355..0ce2e50 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -47,6 +47,8 @@
                                             test::AttributeBuilder()
                                                 .SetTypeMask(android::ResTable_map::TYPE_STRING)
                                                 .Build())
+                           .AddPublicSymbol("android:attr/angle", ResourceId(0x01010004),
+                                            test::AttributeBuilder().Build())
 
                            // Add one real symbol that was introduces in v21
                            .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
@@ -75,7 +77,7 @@
   }
 
  protected:
-  std::unique_ptr<IAaptContext> context_;
+  std::unique_ptr<test::Context> context_;
 };
 
 TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
@@ -254,4 +256,63 @@
   EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id);
 }
 
+
+TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+    <gradient />)");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, NotNull());
+  ASSERT_TRUE(xml_attr->compiled_attribute);
+  EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+  BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+  EXPECT_EQ(value->value.data, 0U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+  <gradient xmlns:android="http://schemas.android.com/apk/res/android"
+      android:angle="90"/>)");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, NotNull());
+  ASSERT_TRUE(xml_attr->compiled_attribute);
+  EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id);
+
+  BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get());
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC);
+  EXPECT_EQ(value->value.data, 90U);
+}
+
+TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+  <gradient xmlns:android="http://schemas.android.com/apk/res/android" />)");
+  context_->SetMinSdkVersion(30);
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  xml::Element* gradient_el = doc->root.get();
+  ASSERT_THAT(gradient_el, NotNull());
+
+  xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle");
+  ASSERT_THAT(xml_attr, IsNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 7e10a59..553c43e 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -81,6 +81,10 @@
     return min_sdk_version_;
   }
 
+  void SetMinSdkVersion(int min_sdk_version) {
+    min_sdk_version_ = min_sdk_version;
+  }
+
  const std::set<std::string>& GetSplitNameDependencies() override {
     return split_name_dependencies_;
   }
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 0f932f3..fa2b41a 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -107,7 +107,7 @@
         println(CODEGEN_VERSION)
         System.exit(0)
     }
-    val file = File(args.last())
+    val file = File(args.last()).absoluteFile
     val sourceLinesNoClosingBrace = file.readLines().dropLastWhile {
         it.startsWith("}") || it.all(Char::isWhitespace)
     }
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 7d50ad1..b2cc813 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
 package com.android.codegen
 
 const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.0"
+const val CODEGEN_VERSION = "1.0.1"
 
 const val CANONICAL_BUILDER_CLASS = "Builder"
 const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp
new file mode 100644
index 0000000..a86c226
--- /dev/null
+++ b/tools/protologtool/Android.bp
@@ -0,0 +1,28 @@
+java_binary_host {
+    name: "protologtool",
+    manifest: "manifest.txt",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "javaparser",
+        "windowmanager-log-proto",
+        "jsonlib",
+    ],
+}
+
+java_test_host {
+    name: "protologtool-tests",
+    test_suites: ["general-tests"],
+    srcs: [
+        "src/**/*.kt",
+        "tests/**/*.kt",
+    ],
+    static_libs: [
+        "javaparser",
+        "windowmanager-log-proto",
+        "jsonlib",
+        "junit",
+        "mockito",
+    ],
+}
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
new file mode 100644
index 0000000..3439357
--- /dev/null
+++ b/tools/protologtool/README.md
@@ -0,0 +1,106 @@
+# ProtoLogTool
+
+Code transformation tool and viewer for ProtoLog.
+
+## What does it do?
+
+ProtoLogTool incorporates three different modes of operation:
+
+### Code transformation
+
+Command: `process <protolog class path> <protolog implementation class path>
+ <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
+
+In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
+```java
+ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
+```
+into:
+```java
+if (GROUP_NAME.isLogToAny()) {
+    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
+}
+```
+where `ProtoLog`, `ProtoLogImpl` and `ProtoLogGroup` are the classes provided as arguments
+ (can be imported, static imported or full path, wildcard imports are not allowed) and, `x` is the
+ logging method. The transformation is done on the source level. A hash is generated from the format
+ string and log level and inserted after the `ProtoLogGroup` argument. The format string is replaced
+ by `null` if `ProtoLogGroup.GROUP_NAME.isLogToLogcat()` returns false. If `ProtoLogGroup.GROUP_NAME.isEnabled()`
+ returns false the log statement is removed entirely from the resultant code.
+
+Input is provided as a list of java source file names. Transformed source is saved to a single
+source jar file. The ProtoLogGroup class with all dependencies should be provided as a compiled
+jar file (config.jar).
+
+### Viewer config generation
+
+Command: `viewerconf <protolog class path> <protolog implementation class path
+<protolog groups class path> <config.jar> [<input.java>] <output.json>`
+
+This command is similar in it's syntax to the previous one, only instead of creating a processed source jar
+it writes a viewer configuration file with following schema:
+```json
+{
+  "version": "1.0.0",
+  "messages": {
+    "123456": {
+      "message": "Format string %d %s",
+      "level": "ERROR",
+      "group": "GROUP_NAME"
+    },
+  },
+  "groups": {
+    "GROUP_NAME": {
+      "tag": "TestLog"
+    }
+  }
+}
+
+```
+
+### Binary log viewing
+
+Command: `read <viewer.json> <wm_log.pb>`
+
+Reads the binary ProtoLog log file and outputs a human-readable LogCat-like text log.
+
+## What is ProtoLog?
+
+ProtoLog is a logging system created for the WindowManager project. It allows both binary and text logging
+and is tunable in runtime. It consists of 3 different submodules:
+* logging system built-in the Android app,
+* log viewer for reading binary logs,
+* a code processing tool.
+
+ProtoLog is designed to reduce both application size (and by that memory usage) and amount of resources needed
+for logging. This is achieved by replacing log message strings with their hashes and only loading to memory/writing
+full log messages when necessary.
+
+### Text logging
+
+For text-based logs Android LogCat is used as a backend. Message strings are loaded from a viewer config
+located on the device when needed.
+
+### Binary logging
+
+Binary logs are saved as Protocol Buffers file. They can be read using the ProtoLog tool or specialised
+viewer like Winscope.
+
+## How to use ProtoLog?
+
+### Adding a new logging group or log statement
+
+To add a new ProtoLogGroup simple create a new enum ProtoLogGroup member with desired parameters.
+
+To add a new logging statement just add a new call to ProtoLog.x where x is a log level.
+
+After doing any changes to logging groups or statements you should run `make update-protolog` to update
+viewer configuration saved in the code repository.
+
+## How to change settings on device in runtime?
+Use the `adb shell su root cmd window logging` command. To get help just type
+`adb shell su root cmd window logging help`.
+
+
+
+
diff --git a/tools/protologtool/TEST_MAPPING b/tools/protologtool/TEST_MAPPING
new file mode 100644
index 0000000..52b12dc
--- /dev/null
+++ b/tools/protologtool/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "protologtool-tests"
+    }
+  ]
+}
diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt
new file mode 100644
index 0000000..f5e53c4
--- /dev/null
+++ b/tools/protologtool/manifest.txt
@@ -0,0 +1 @@
+Main-class: com.android.protologtool.ProtoLogTool
diff --git a/tools/protologtool/src/com/android/protologtool/CodeUtils.kt b/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
new file mode 100644
index 0000000..facca62
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/CodeUtils.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
new file mode 100644
index 0000000..df49e15
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/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.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/core/java/android/view/Transaction.aidl b/tools/protologtool/src/com/android/protologtool/Constants.kt
similarity index 61%
copy from core/java/android/view/Transaction.aidl
copy to tools/protologtool/src/com/android/protologtool/Constants.kt
index 8d24b43..2ccfc4d 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/tools/protologtool/src/com/android/protologtool/Constants.kt
@@ -14,6 +14,14 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+package com.android.protologtool
 
-parcelable Transaction;
+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/core/java/android/view/Transaction.aidl b/tools/protologtool/src/com/android/protologtool/LogGroup.kt
similarity index 79%
copy from core/java/android/view/Transaction.aidl
copy to tools/protologtool/src/com/android/protologtool/LogGroup.kt
index 8d24b43..42a37a2 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/tools/protologtool/src/com/android/protologtool/LogGroup.kt
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+package com.android.protologtool
 
-parcelable Transaction;
+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
new file mode 100644
index 0000000..dc29557
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/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.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
new file mode 100644
index 0000000..4d0eb0e
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/LogParser.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
new file mode 100644
index 0000000..29d8ae5
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/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.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/core/java/android/view/Transaction.aidl b/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
similarity index 73%
copy from core/java/android/view/Transaction.aidl
copy to tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
index 8d24b43..42a75f8 100644
--- a/core/java/android/view/Transaction.aidl
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package android.view.SurfaceControl;
+package com.android.protologtool
 
-parcelable Transaction;
+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
new file mode 100644
index 0000000..664c8a6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
new file mode 100644
index 0000000..618e4b1
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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 text = file.readText()
+            val code = StaticJavaParser.parse(text)
+            val outSrc = transformer.processClass(text, 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
new file mode 100644
index 0000000..f915ea6
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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)) + '}'
+        // 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)
+    }
+
+    private val protoLogImplClassNode =
+            StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+    private var processedCode: MutableList<String> = mutableListOf()
+    private var offsets: IntArray = IntArray(0)
+
+    fun processClass(
+        code: String,
+        compilationUnit: CompilationUnit =
+               StaticJavaParser.parse(code)
+    ): String {
+        processedCode = code.split('\n').toMutableList()
+        offsets = IntArray(processedCode.size)
+        LexicalPreservingPrinter.setup(compilationUnit)
+        protoLogCallProcessor.process(compilationUnit, this)
+        // return LexicalPreservingPrinter.print(compilationUnit)
+        return processedCode.joinToString("\n")
+    }
+}
diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
new file mode 100644
index 0000000..8ce9a49
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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
new file mode 100644
index 0000000..69cf92d
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
new file mode 100644
index 0000000..2199785
--- /dev/null
+++ b/tools/protologtool/src/com/android/protologtool/exceptions.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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/protologtool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
new file mode 100644
index 0000000..82daa73
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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
new file mode 100644
index 0000000..c1cd473
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/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.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
new file mode 100644
index 0000000..7106ea6
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/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.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
new file mode 100644
index 0000000..dcb1f7f
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/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.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
new file mode 100644
index 0000000..2cd8562
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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"
+
+        /* ktlint-disable max-line-length */
+        private val TEST_CODE = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_MULTILINE = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f " + 
+                    "abc %s\n test", 100,
+                     0.1, "test");
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_MULTICALLS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                    ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1);
+                }
+            }
+            """.trimIndent()
+
+        private val TEST_CODE_NO_PARAMS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    ProtoLog.w(TEST_GROUP, "test");
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_NO_PARAMS = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { org.example.ProtoLogImpl.w(TEST_GROUP, 1282022424, 0, "test", (Object[]) null); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, 835524026, 9, null, protoLogParam0, protoLogParam1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (TEST_GROUP.isLogToAny()) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -986393606, 9, null, protoLogParam0, protoLogParam1, protoLogParam2); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); }
+                }
+            }
+            """.trimIndent()
+
+        private val TRANSFORMED_CODE_MULTILINE_DISABLED = """
+            package org.example;
+
+            class Test {
+                void test() {
+                    if (false) { /* TEST_GROUP is disabled */ ProtoLog.w(TEST_GROUP, "test %d %f " + "abc %s\n test", 100, 0.1, "test"); 
+            
+            }
+                }
+            }
+            """.trimIndent()
+        /* ktlint-enable max-line-length */
+    }
+
+    private val processor: ProtoLogCallProcessor = Mockito.mock(ProtoLogCallProcessor::class.java)
+    private val sourceJarWriter = SourceTransformer("org.example.ProtoLogImpl", processor)
+
+    private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+    @Test
+    fun processClass_textEnabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_textEnabledMulticalls() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            val calls = code.findAll(MethodCallExpr::class.java)
+            visitor.processCall(calls[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+            visitor.processCall(calls[1], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+            visitor.processCall(calls[2], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTICALLS, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(3, ifStmts.size)
+        val ifStmt = ifStmts[1]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_textEnabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(4, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(7, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("-986393606", methodCall.arguments[1].toString())
+        assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
+    }
+
+    @Test
+    fun processClass_noParams() {
+        var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_NO_PARAMS, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(1, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(5, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("1282022424", methodCall.arguments[1].toString())
+        assertEquals(0.toString(), methodCall.arguments[2].toString())
+        assertEquals(TRANSFORMED_CODE_NO_PARAMS, out)
+    }
+
+    @Test
+    fun processClass_textDisabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", true, false, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(3, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[0] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(6, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("835524026", methodCall.arguments[1].toString())
+        assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
+        assertEquals("null", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_textDisabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    true, false, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("TEST_GROUP.${Constants.IS_LOG_TO_ANY_METHOD}()",
+                ifStmt.condition.toString())
+        assertFalse(ifStmt.elseStmt.isPresent)
+        assertEquals(4, ifStmt.thenStmt.childNodes.size)
+        val methodCall = ifStmt.thenStmt.findAll(MethodCallExpr::class.java)[1] as MethodCallExpr
+        assertEquals(PROTO_LOG_IMPL_PATH, methodCall.scope.get().toString())
+        assertEquals("w", methodCall.name.asString())
+        assertEquals(7, methodCall.arguments.size)
+        assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
+        assertEquals("-986393606", methodCall.arguments[1].toString())
+        assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
+        assertEquals("null", methodCall.arguments[3].toString())
+        assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+        assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+        assertEquals("protoLogParam2", methodCall.arguments[6].toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_disabled() {
+        var code = StaticJavaParser.parse(TEST_CODE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
+                    LogLevel.WARN, LogGroup("TEST_GROUP", false, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("false", ifStmt.condition.toString())
+        assertEquals(TRANSFORMED_CODE_DISABLED, out)
+    }
+
+    @Test
+    fun processClass_disabledMultiline() {
+        var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
+
+        Mockito.`when`(processor.process(any(CompilationUnit::class.java),
+                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+            val visitor = invocation.arguments[1] as ProtoLogCallVisitor
+
+            visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
+                    "test %d %f abc %s\n test", LogLevel.WARN, LogGroup("TEST_GROUP",
+                    false, true, "WM_TEST"))
+
+            invocation.arguments[0] as CompilationUnit
+        }
+
+        val out = sourceJarWriter.processClass(TEST_CODE_MULTILINE, code)
+        code = StaticJavaParser.parse(out)
+
+        val ifStmts = code.findAll(IfStmt::class.java)
+        assertEquals(1, ifStmts.size)
+        val ifStmt = ifStmts[0]
+        assertEquals("false", ifStmt.condition.toString())
+        assertEquals(TRANSFORMED_CODE_MULTILINE_DISABLED, out)
+    }
+}
diff --git a/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
new file mode 100644
index 0000000..53d2e8b
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.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
new file mode 100644
index 0000000..c0cea73
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protologtool/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.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/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 9d4b837..f8c2011 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -15,6 +15,7 @@
  */
 package android.net.wifi;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
@@ -23,6 +24,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -111,6 +114,48 @@
     /** @hide */
     public static final String CA_CERT_ALIAS_DELIMITER = " ";
 
+    /**
+     * Do not use OCSP stapling (TLS certificate status extension)
+     * @hide
+     */
+    public static final int OCSP_NONE = 0;
+
+    /**
+     * Try to use OCSP stapling, but not require response
+     * @hide
+     */
+    public static final int OCSP_REQUEST_CERT_STATUS = 1;
+
+    /**
+     * Require valid OCSP stapling response
+     * @hide
+     */
+    public static final int OCSP_REQUIRE_CERT_STATUS = 2;
+
+    /**
+     * Require valid OCSP stapling response for all not-trusted certificates in the server
+     * certificate chain
+     * @hide
+     */
+    public static final int OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS = 3;
+
+    /** @hide */
+    @IntDef(prefix = {"OCSP_"}, value = {
+            OCSP_NONE,
+            OCSP_REQUEST_CERT_STATUS,
+            OCSP_REQUIRE_CERT_STATUS,
+            OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Ocsp {
+    }
+
+    /**
+     * Whether to use/require OCSP (Online Certificate Status Protocol) to check server certificate.
+     * @hide
+     */
+    private @Ocsp int mOcsp = OCSP_NONE;
+
     // Fields to copy verbatim from wpa_supplicant.
     private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] {
             IDENTITY_KEY,
@@ -185,6 +230,7 @@
         mPhase2Method = source.mPhase2Method;
         mIsAppInstalledDeviceKeyAndCert = source.mIsAppInstalledDeviceKeyAndCert;
         mIsAppInstalledCaCert = source.mIsAppInstalledCaCert;
+        mOcsp = source.mOcsp;
     }
 
     /**
@@ -230,6 +276,7 @@
         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
         dest.writeBoolean(mIsAppInstalledDeviceKeyAndCert);
         dest.writeBoolean(mIsAppInstalledCaCert);
+        dest.writeInt(mOcsp);
     }
 
     public static final @android.annotation.NonNull Creator<WifiEnterpriseConfig> CREATOR =
@@ -251,6 +298,7 @@
                     enterpriseConfig.mClientCertificateChain = ParcelUtil.readCertificates(in);
                     enterpriseConfig.mIsAppInstalledDeviceKeyAndCert = in.readBoolean();
                     enterpriseConfig.mIsAppInstalledCaCert = in.readBoolean();
+                    enterpriseConfig.mOcsp = in.readInt();
                     return enterpriseConfig;
                 }
 
@@ -1141,6 +1189,7 @@
         if (mPhase2Method > 0 && mPhase2Method < Phase2.strings.length) {
             sb.append("phase2_method: ").append(Phase2.strings[mPhase2Method]).append("\n");
         }
+        sb.append(" ocsp: ").append(mOcsp).append("\n");
         return sb.toString();
     }
 
@@ -1190,4 +1239,28 @@
     public boolean isAppInstalledCaCert() {
         return mIsAppInstalledCaCert;
     }
+
+    /**
+     * Set the ocsp type.
+     * @param  ocsp is one {@link ##OCSP_NONE}, {@link #OCSP_REQUEST_CERT_STATUS},
+     *                   {@link #OCSP_REQUIRE_CERT_STATUS} or
+     *                   {@link #OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS}
+     * @hide
+     */
+    public void setOcsp(@Ocsp int ocsp) {
+        if (ocsp >= OCSP_NONE && ocsp <= OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS) {
+            mOcsp = ocsp;
+        } else {
+            throw new IllegalArgumentException("Invalid OCSP type.");
+        }
+    }
+
+    /**
+     * Get the ocsp type.
+     * @return ocsp type
+     * @hide
+     */
+    public @Ocsp int getOcsp() {
+        return mOcsp;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6bf7bfb9..0fa0ec7 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -52,12 +52,14 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.WorkSource;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.server.net.NetworkPinner;
@@ -1730,13 +1732,12 @@
      * <li> When an app is uninstalled, all its suggested networks are discarded. If the device is
      * currently connected to a suggested network which is being removed then the device will
      * disconnect from that network.</li>
-     * <li> No in-place modification of existing suggestions are allowed. Apps are expected to
-     * remove suggestions using {@link #removeNetworkSuggestions(List)} and then add the modified
-     * suggestion back using this API.</li>
+     * <li> In-place modification of existing suggestions are allowed.
+     * If the provided suggestions {@link WifiNetworkSuggestion#equals(Object)} any previously
+     * provided suggestions by the app. Previous suggestions will be updated</li>
      *
      * @param networkSuggestions List of network suggestions provided by the app.
      * @return Status code for the operation. One of the STATUS_NETWORK_SUGGESTIONS_ values.
-     * {@link WifiNetworkSuggestion#equals(Object)} any previously provided suggestions by the app.
      * @throws {@link SecurityException} if the caller is missing required permissions.
      */
     @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE)
@@ -1821,6 +1822,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (config == null) return;
                 throw new RemoteException("Wifi service is not running");
             }
             if (!iWifiManager.addOrUpdatePasspointConfiguration(
@@ -1849,6 +1851,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(fqdn)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             if (!iWifiManager.removePasspointConfiguration(
@@ -1899,6 +1902,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (bssid == 0L || TextUtils.isEmpty(fileName)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.queryPasspointIcon(bssid, fileName);
@@ -1928,6 +1932,8 @@
      * @param holdoff hold off time in milliseconds
      * @param ess set if the hold off pertains to an ESS rather than a BSS
      * @hide
+     *
+     * TODO (140167680): This needs to be removed, the implementation is empty!
      */
     public void deauthenticateNetwork(long holdoff, boolean ess) {
         try {
@@ -2504,6 +2510,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(country)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.setCountryCode(country);
@@ -2692,6 +2699,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(ifaceName) || mode == IFACE_IP_MODE_UNSPECIFIED) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.updateInterfaceIpState(ifaceName, mode);
@@ -3044,6 +3052,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (remoteIPAddress == null || !enable) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableTdls(remoteIPAddress.getHostAddress(), enable);
@@ -3062,6 +3071,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (TextUtils.isEmpty(remoteMacAddress) || !enable) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableTdlsWithMacAddress(remoteMacAddress, enable);
@@ -3956,7 +3966,7 @@
             android.Manifest.permission.NETWORK_STACK
     })
     public void disableEphemeralNetwork(String SSID) {
-        if (SSID == null) throw new IllegalArgumentException("SSID cannot be null");
+        if (TextUtils.isEmpty(SSID)) throw new IllegalArgumentException("SSID cannot be null");
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
@@ -4495,6 +4505,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (verbose == 0) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableVerboseLogging(verbose);
@@ -4581,6 +4592,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (enabled) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.enableWifiConnectivityManager(enabled);
@@ -4611,6 +4623,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (ArrayUtils.isEmpty(data)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.restoreBackupData(data);
@@ -4631,6 +4644,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (ArrayUtils.isEmpty(supplicantData) && ArrayUtils.isEmpty(ipConfigData)) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.restoreSupplicantBackupData(supplicantData, ipConfigData);
@@ -4937,6 +4951,7 @@
         try {
             IWifiManager iWifiManager = getIWifiManager();
             if (iWifiManager == null) {
+                if (state == DEVICE_MOBILITY_STATE_UNKNOWN) return;
                 throw new RemoteException("Wifi service is not running");
             }
             iWifiManager.setDeviceMobilityState(state);
diff --git a/wifi/java/android/net/wifi/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/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index beed666..d8db4ce 100644
--- a/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
@@ -343,11 +344,13 @@
         enterpriseConfig.setPassword("*");
         enterpriseConfig.setEapMethod(Eap.TTLS);
         enterpriseConfig.setPhase2Method(Phase2.GTC);
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
         mEnterpriseConfig = new WifiEnterpriseConfig();
         mEnterpriseConfig.copyFromExternal(enterpriseConfig, "*");
         assertEquals("TTLS", getSupplicantEapMethod());
         assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
         assertNotEquals("*", mEnterpriseConfig.getPassword());
+        assertEquals(enterpriseConfig.getOcsp(), mEnterpriseConfig.getOcsp());
     }
 
     /** Verfies that parceling a WifiEnterpriseConfig preseves method information. */
@@ -487,4 +490,35 @@
         assertFalse(mEnterpriseConfig.isAppInstalledDeviceKeyAndCert());
         assertTrue(mEnterpriseConfig.isAppInstalledCaCert());
     }
+
+    /** Verifies that OCSP value is set correctly. */
+    @Test
+    public void testOcspSetGet() throws Exception {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE);
+        assertEquals(WifiEnterpriseConfig.OCSP_NONE, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUIRE_CERT_STATUS, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUEST_CERT_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUEST_CERT_STATUS, enterpriseConfig.getOcsp());
+
+        enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS);
+        assertEquals(WifiEnterpriseConfig.OCSP_REQUIRE_ALL_NON_TRUSTED_CERTS_STATUS,
+                enterpriseConfig.getOcsp());
+    }
+
+    /** Verifies that an exception is thrown when invalid OCSP is set. */
+    @Test
+    public void testInvalidOcspValue() {
+        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
+        try {
+            enterpriseConfig.setOcsp(-1);
+            fail("Should raise an IllegalArgumentException here.");
+        } catch (IllegalArgumentException e) {
+            // expected exception.
+        }
+    }
 }