Merge "Use Sim state cache when available"
diff --git a/Android.bp b/Android.bp
index 69f5595..632ef1c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -112,6 +112,14 @@
 }
 
 filegroup {
+    name: "framework-mime-sources",
+    srcs: [
+        "mime/java/**/*.java",
+    ],
+    path: "mime/java",
+}
+
+filegroup {
     name: "framework-opengl-sources",
     srcs: [
         "opengl/java/**/*.java",
@@ -176,6 +184,7 @@
         ":framework-mca-effect-sources",
         ":framework-mca-filterfw-sources",
         ":framework-mca-filterpacks-sources",
+        ":framework-mime-sources",
         ":framework-opengl-sources",
         ":framework-rs-sources",
         ":framework-sax-sources",
@@ -316,7 +325,10 @@
 
     jarjar_rules: ":framework-jarjar-rules",
 
-    static_libs: ["framework-internal-utils"],
+    static_libs: [
+        "framework-internal-utils",
+        "mimemap",
+    ],
 
     dxflags: [
         "--core-library",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index c2bdb6c..2f5f555 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1157,22 +1157,14 @@
         private void removeAll(Predicate<JobStatus> predicate) {
             for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
                 final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex);
-                for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
-                    if (predicate.test(jobs.valueAt(jobIndex))) {
-                        jobs.removeAt(jobIndex);
-                    }
-                }
+                jobs.removeIf(predicate);
                 if (jobs.size() == 0) {
                     mJobs.removeAt(jobSetIndex);
                 }
             }
             for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
                 final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex);
-                for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) {
-                    if (predicate.test(jobs.valueAt(jobIndex))) {
-                        jobs.removeAt(jobIndex);
-                    }
-                }
+                jobs.removeIf(predicate);
                 if (jobs.size() == 0) {
                     mJobsPerSourceUid.removeAt(jobSetIndex);
                 }
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
new file mode 100644
index 0000000..786e8b0
--- /dev/null
+++ b/apex/statsd/service/Android.bp
@@ -0,0 +1,16 @@
+// Statsd Service jar, which will eventually be put in the statsd mainline apex.
+// statsd-service needs to be added to PRODUCT_SYSTEM_SERVER_JARS.
+// This jar will contain StatsCompanionService
+java_library {
+    name: "statsd-service",
+    installable: true,
+
+    srcs: [
+        "java/**/*.java",
+    ],
+
+    libs: [
+        "framework",
+        "services.core",
+    ],
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
similarity index 100%
rename from services/core/java/com/android/server/stats/StatsCompanionService.java
rename to apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
diff --git a/api/current.txt b/api/current.txt
index fc7685d..44cb997 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44291,6 +44291,7 @@
     field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
     field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
     field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
+    field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
     field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
     field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
     field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
diff --git a/api/system-current.txt b/api/system-current.txt
index 279d2c8..e92dc89 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5901,7 +5901,8 @@
     field public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
     field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
     field public static final String NAMESPACE_SCHEDULER = "scheduler";
-    field public static final String NAMESPACE_STORAGE = "storage";
+    field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
+    field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
     field public static final String NAMESPACE_SYSTEMUI = "systemui";
     field public static final String NAMESPACE_TELEPHONY = "telephony";
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
@@ -7858,6 +7859,8 @@
     method public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
+    method public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber);
+    method public void onOutgoingEmergencySms(@NonNull android.telephony.emergency.EmergencyNumber);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseCallStateChanged(@NonNull android.telephony.PreciseCallState);
     method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
     method public void onRadioPowerStateChanged(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 9b00a42..ee631be 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2890,6 +2890,11 @@
     method public static void setMinMatchForTest(int);
   }
 
+  public class PhoneStateListener {
+    field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER = 268435456; // 0x10000000
+    field @RequiresPermission("android.permission.READ_ACTIVE_EMERGENCY_SESSION") public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER = 536870912; // 0x20000000
+  }
+
   public class ServiceState implements android.os.Parcelable {
     method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
     method public void setCdmaSystemAndNetworkId(int, int);
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index e0f7d86..b37ee74 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -65,6 +65,14 @@
     private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
     private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
     private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
+    /**
+     * Change the system dialer package name if a package name was specified,
+     * Example: adb shell telecom set-system-dialer <PACKAGE>
+     *
+     * Restore it to the default if if argument is "default" or no argument is passed.
+     * Example: adb shell telecom set-system-dialer default
+     */
+    private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
     private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
     private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
     private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
@@ -193,6 +201,9 @@
             case COMMAND_GET_DEFAULT_DIALER:
                 runGetDefaultDialer();
                 break;
+            case COMMAND_SET_SYSTEM_DIALER:
+                runSetSystemDialer();
+                break;
             case COMMAND_GET_SYSTEM_DIALER:
                 runGetSystemDialer();
                 break;
@@ -297,6 +308,12 @@
         System.out.println("Success - " + packageName + " set as override default dialer.");
     }
 
+    private void runSetSystemDialer() throws RemoteException {
+        final String packageName = nextArg();
+        mTelecomService.setSystemDialerPackage(packageName.equals("default") ? null : packageName);
+        System.out.println("Success - " + packageName + " set as override system dialer.");
+    }
+
     private void runGetDefaultDialer() throws RemoteException {
         System.out.println(mTelecomService.getDefaultDialerPackage());
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index cb99a3a..7f597fe 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -188,57 +188,6 @@
     final ArrayMap<OnUidImportanceListener, UidObserver> mImportanceListeners = new ArrayMap<>();
 
     /**
-     * Defines acceptable types of bugreports.
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "BUGREPORT_OPTION_" }, value = {
-            BUGREPORT_OPTION_FULL,
-            BUGREPORT_OPTION_INTERACTIVE,
-            BUGREPORT_OPTION_REMOTE,
-            BUGREPORT_OPTION_WEAR,
-            BUGREPORT_OPTION_TELEPHONY,
-            BUGREPORT_OPTION_WIFI
-    })
-    public @interface BugreportMode {}
-    /**
-     * Takes a bugreport without user interference (and hence causing less
-     * interference to the system), but includes all sections.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_FULL = 0;
-    /**
-     * Allows user to monitor progress and enter additional data; might not include all
-     * sections.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_INTERACTIVE = 1;
-    /**
-     * Takes a bugreport requested remotely by administrator of the Device Owner app,
-     * not the device's user.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_REMOTE = 2;
-    /**
-     * Takes a bugreport on a wearable device.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_WEAR = 3;
-
-    /**
-     * Takes a lightweight version of bugreport that only includes a few, urgent sections
-     * used to report telephony bugs.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_TELEPHONY = 4;
-
-    /**
-     * Takes a lightweight bugreport that only includes a few sections related to Wifi.
-     * @hide
-     */
-    public static final int BUGREPORT_OPTION_WIFI = 5;
-
-    /**
      * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
      * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
      * uninstalled in lieu of the declaring one.  The package named here must be
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 8765760..b873be3 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -153,4 +153,11 @@
      * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
      */
     protected abstract DevicePolicyCache getDevicePolicyCache();
+
+    /**
+     * @return cached version of device state related to DPM that can be accessed without risking
+     * deadlocks.
+     * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
+     */
+    protected abstract DeviceStateCache getDeviceStateCache();
 }
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
new file mode 100644
index 0000000..7619aa2
--- /dev/null
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.admin;
+
+import com.android.server.LocalServices;
+
+/**
+ * Stores a copy of the set of device state maintained by {@link DevicePolicyManager} which
+ * is not directly related to admin policies. This lives in its own class so that the state
+ * can be accessed from any place without risking dead locks.
+ *
+ * @hide
+ */
+public abstract class DeviceStateCache {
+    protected DeviceStateCache() {
+    }
+
+    /**
+     * @return the instance.
+     */
+    public static DeviceStateCache getInstance() {
+        final DevicePolicyManagerInternal dpmi =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        return (dpmi != null) ? dpmi.getDeviceStateCache() : EmptyDeviceStateCache.INSTANCE;
+    }
+
+    /**
+     * See {@link DevicePolicyManager#isDeviceProvisioned}
+     */
+    public abstract boolean isDeviceProvisioned();
+
+    /**
+     * Empty implementation.
+     */
+    private static class EmptyDeviceStateCache extends DeviceStateCache {
+        private static final EmptyDeviceStateCache INSTANCE = new EmptyDeviceStateCache();
+
+        @Override
+        public boolean isDeviceProvisioned() {
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index a5b71f6..59fb3d9 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -86,12 +86,22 @@
     public abstract void setDeviceManaged(boolean isManaged);
 
     /**
+     * Returns whether the device is managed by device owner.
+     */
+    public abstract boolean isDeviceManaged();
+
+    /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
      * whether the user is managed by profile owner.
      */
     public abstract void setUserManaged(int userId, boolean isManaged);
 
     /**
+     * whether a profile owner manages this user.
+     */
+    public abstract boolean isUserManaged(int userId);
+
+    /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to omit
      * restriction check, because DevicePolicyManager must always be able to set user icon
      * regardless of any restriction.
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 2b3a2ab..4dd9bab 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -151,7 +151,7 @@
                 .PRIORITY_CATEGORY_ALARMS) != 0;
         mAllowMedia = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
                 .PRIORITY_CATEGORY_MEDIA) != 0;
-        mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(
+        mAllowRinger = !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
                 mNotificationPolicy);
         mStreamType = streamType;
         mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
@@ -571,7 +571,7 @@
                         .PRIORITY_CATEGORY_ALARMS) != 0;
                 mAllowMedia = (mNotificationPolicy.priorityCategories
                         & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) != 0;
-                mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(
+                mAllowRinger = !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
                         mNotificationPolicy);
                 updateSlider();
             }
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 5e201e4..fd1381a 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -257,10 +257,20 @@
     /**
      * Namespace for storage-related features.
      *
+     * @deprecated Replace storage namespace with storage_native_boot.
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public static final String NAMESPACE_STORAGE = "storage";
+
+    /**
+     * Namespace for storage-related features, including native and boot.
+     *
      * @hide
      */
     @SystemApi
-    public static final String NAMESPACE_STORAGE = "storage";
+    public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
 
     /**
      * Namespace for System UI related features.
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 937990f7..1f2c872 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1904,10 +1904,10 @@
     }
 
     /**
-     * Determines whether dnd behavior should mute all notification/ringer sounds
-     * (sounds associated with ringer volume discluding system)
+     * Determines whether dnd behavior should mute all ringer-controlled sounds
+     * This includes notification, ringer and system sounds
      */
-    public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(NotificationManager.Policy
+    public static boolean areAllPriorityOnlyRingerSoundsMuted(NotificationManager.Policy
             policy) {
         boolean allowReminders = (policy.priorityCategories
                 & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
@@ -1920,20 +1920,19 @@
         boolean allowRepeatCallers = (policy.priorityCategories
                 & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0;
         boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
+        boolean allowSystem =  (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
         return !allowReminders && !allowCalls && !allowMessages && !allowEvents
-                && !allowRepeatCallers && !areChannelsBypassingDnd;
+                && !allowRepeatCallers && !areChannelsBypassingDnd && !allowSystem;
     }
 
     /**
-     * Determines whether dnd behavior should mute all sounds controlled by ringer
+     * Determines whether dnd behavior should mute all sounds
      */
     public static boolean areAllZenBehaviorSoundsMuted(NotificationManager.Policy
             policy) {
         boolean allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
         boolean allowMedia = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MEDIA) != 0;
-        boolean allowSystem = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_SYSTEM) != 0;
-        return !allowAlarms && !allowMedia && !allowSystem
-                && areAllPriorityOnlyNotificationZenSoundsMuted(policy);
+        return !allowAlarms && !allowMedia && areAllPriorityOnlyRingerSoundsMuted(policy);
     }
 
     /**
@@ -1943,24 +1942,25 @@
         return zen == Global.ZEN_MODE_NO_INTERRUPTIONS
                 || zen == Global.ZEN_MODE_ALARMS
                 || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                && ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(consolidatedPolicy));
+                && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(consolidatedPolicy));
     }
 
     /**
-     * Determines whether dnd behavior should mute all sounds controlled by ringer
+     * Determines whether dnd behavior should mute all ringer-controlled sounds
+     * This includes notification, ringer and system sounds
      */
-    public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) {
+    public static boolean areAllPriorityOnlyRingerSoundsMuted(ZenModeConfig config) {
         return !config.allowReminders && !config.allowCalls && !config.allowMessages
                 && !config.allowEvents && !config.allowRepeatCallers
-                && !config.areChannelsBypassingDnd;
+                && !config.areChannelsBypassingDnd && !config.allowSystem;
     }
 
     /**
-     * Determines whether all dnd mutes all sounds
+     * Determines whether dnd mutes all sounds
      */
     public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) {
-        return !config.allowAlarms  && !config.allowMedia && !config.allowSystem
-                && areAllPriorityOnlyNotificationZenSoundsMuted(config);
+        return !config.allowAlarms  && !config.allowMedia
+                && areAllPriorityOnlyRingerSoundsMuted(config);
     }
 
     /**
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index e72ff38..d47604d 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -37,7 +37,7 @@
  * View.startDragAndDrop} by the app that started the drag operation.
  * </p>
  * <p>
- * The life cycle of the permissions is bound to the activity used to call {@link
+ * The lifecycle of the permissions is bound to the activity used to call {@link
  * android.app.Activity#requestDragAndDropPermissions(DragEvent) requestDragAndDropPermissions}. The
  * permissions are revoked when this activity is destroyed, or when {@link #release()} is called,
  * whichever occurs first.
@@ -49,6 +49,10 @@
  * {@link Activity#onSaveInstanceState} bundle and later retrieved in order to manually release
  * the permissions once they are no longer needed.
  * </p>
+ * <p>
+ * Learn more about <a href="/guide/topics/ui/drag-drop#DragPermissionsMultiWindow">drag permissions
+ * in multi-window mode</a>.
+ * </p>
  */
 public final class DragAndDropPermissions implements Parcelable {
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index d5559aa..6639fbf 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -187,8 +187,7 @@
 
     private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
             InputWindowHandle handle);
-    private static native void nativeTransferTouchFocus(long transactionObj, IBinder fromToken,
-            IBinder toToken);
+
     private static native boolean nativeGetProtectedContentSupport();
     private static native void nativeSetMetadata(long transactionObj, long nativeObject, int key,
             Parcel data);
@@ -2239,22 +2238,6 @@
         }
 
         /**
-         * Transfers touch focus from one window to another. It is possible for multiple windows to
-         * have touch focus if they support split touch dispatch
-         * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
-         * method only transfers touch focus of the specified window without affecting
-         * other windows that may also have touch focus at the same time.
-         * @param fromToken The token of a window that currently has touch focus.
-         * @param toToken The token of the window that should receive touch focus in
-         * place of the first.
-         * @hide
-         */
-        public Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
-            nativeTransferTouchFocus(mNativeObject, fromToken, toToken);
-            return this;
-        }
-
-        /**
          * Waits until any changes to input windows have been sent from SurfaceFlinger to
          * InputFlinger before returning.
          *
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 18d4d69..7282008 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -193,26 +193,24 @@
     }
 
     /**
-     * Used with {@link #setMixedContentMode}
-     *
      * In this mode, the WebView will allow a secure origin to load content from any other origin,
      * even if that origin is insecure. This is the least secure mode of operation for the WebView,
      * and where possible apps should not set this mode.
+     *
+     * @see #setMixedContentMode
      */
     public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0;
 
     /**
-     * Used with {@link #setMixedContentMode}
-     *
      * In this mode, the WebView will not allow a secure origin to load content from an insecure
      * origin. This is the preferred and most secure mode of operation for the WebView and apps are
      * strongly advised to use this mode.
+     *
+     * @see #setMixedContentMode
      */
     public static final int MIXED_CONTENT_NEVER_ALLOW = 1;
 
     /**
-     * Used with {@link #setMixedContentMode}
-     *
      * In this mode, the WebView will attempt to be compatible with the approach of a modern web
      * browser with regard to mixed content. Some insecure content may be allowed to be loaded by
      * a secure origin and other types of content will be blocked. The types of content are allowed
@@ -221,6 +219,8 @@
      * This mode is intended to be used by apps that are not in control of the content that they
      * render but desire to operate in a reasonably secure environment. For highest security, apps
      * are recommended to use {@link #MIXED_CONTENT_NEVER_ALLOW}.
+     *
+     * @see #setMixedContentMode
      */
     public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2;
 
@@ -234,30 +234,30 @@
     public @interface ForceDark {}
 
     /**
-     * Used with {@link #setForceDark}
-     *
      * Disable force dark, irrespective of the force dark mode of the WebView parent. In this mode,
      * WebView content will always be rendered as-is, regardless of whether native views are being
      * automatically darkened.
+     *
+     * @see #setForceDark
      */
     public static final int FORCE_DARK_OFF = 0;
 
     /**
-     * Used with {@link #setForceDark}
-     *
      * Enable force dark dependent on the state of the WebView parent view. If the WebView parent
      * view is being automatically force darkened
      * (see: {@link android.view.View#setForceDarkAllowed}), then WebView content will be rendered
      * so as to emulate a dark theme. WebViews that are not attached to the view hierarchy will not
      * be inverted.
+     *
+     * @see #setForceDark
      */
     public static final int FORCE_DARK_AUTO = 1;
 
     /**
-     * Used with {@link #setForceDark}
-     *
      * Unconditionally enable force dark. In this mode WebView content will always be rendered so
      * as to emulate a dark theme.
+     *
+     * @see #setForceDark
      */
     public static final int FORCE_DARK_ON = 2;
 
@@ -1471,6 +1471,7 @@
      * Set the force dark mode for this WebView.
      *
      * @param forceDark the force dark mode to set.
+     * @see #getForceDark
      */
     public void setForceDark(@ForceDark int forceDark) {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1478,10 +1479,10 @@
 
     /**
      * Get the force dark mode for this WebView.
-     *
-     * The default force dark mode is {@link #FORCE_DARK_AUTO}
+     * The default force dark mode is {@link #FORCE_DARK_AUTO}.
      *
      * @return the currently set force dark mode.
+     * @see #setForceDark
      */
     public @ForceDark int getForceDark() {
         // Stub implementation to satisfy Roboelectrc shadows that don't override this yet.
@@ -1516,34 +1517,34 @@
     public abstract @MenuItemFlags int getDisabledActionModeMenuItems();
 
     /**
-     * Used with {@link #setDisabledActionModeMenuItems}.
-     *
      * No menu items should be disabled.
+     *
+     * @see #setDisabledActionModeMenuItems
      */
     public static final int MENU_ITEM_NONE = 0;
 
     /**
-     * Used with {@link #setDisabledActionModeMenuItems}.
-     *
      * Disable menu item "Share".
+     *
+     * @see #setDisabledActionModeMenuItems
      */
     public static final int MENU_ITEM_SHARE = 1 << 0;
 
     /**
-     * Used with {@link #setDisabledActionModeMenuItems}.
-     *
      * Disable menu item "Web Search".
+     *
+     * @see #setDisabledActionModeMenuItems
      */
     public static final int MENU_ITEM_WEB_SEARCH = 1 << 1;
 
     /**
-     * Used with {@link #setDisabledActionModeMenuItems}.
-     *
      * Disable all the action mode menu items for text processing.
      * By default WebView searches for activities that are able to handle
      * {@link android.content.Intent#ACTION_PROCESS_TEXT} and show them in the
      * action mode menu. If this flag is set via {@link
      * #setDisabledActionModeMenuItems}, these menu items will be disabled.
+     *
+     * @see #setDisabledActionModeMenuItems
      */
     public static final int MENU_ITEM_PROCESS_TEXT = 1 << 2;
 }
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 5ea970d..8283eb7 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -77,10 +77,10 @@
      * @param state    of the reported change - enabled/disabled/only logged
      */
     public void reportChange(int uid, long changeId, int state) {
-        debugLog(uid, changeId, state);
         ChangeReport report = new ChangeReport(uid, changeId, state);
         synchronized (mReportedChanges) {
             if (!mReportedChanges.contains(report)) {
+                debugLog(uid, changeId, state);
                 StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
                         state, mSource);
                 mReportedChanges.add(report);
@@ -89,7 +89,6 @@
     }
 
     private void debugLog(int uid, long changeId, int state) {
-        //TODO(b/138374585): Implement rate limiting for the logs.
         String message = String.format("Compat change id reported: %d; UID %d; state: %s", changeId,
                 uid, stateToString(state));
         if (mSource == StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER) {
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index d6caa09..9fff447 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
+import android.content.type.DefaultMimeMapFactory;
 import android.os.Build;
 import android.os.DeadObjectException;
 import android.os.Debug;
@@ -33,6 +34,9 @@
 import com.android.server.NetworkManagementSocketTagger;
 import dalvik.system.RuntimeHooks;
 import dalvik.system.VMRuntime;
+
+import libcore.net.MimeMap;
+
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -199,6 +203,13 @@
     public static void preForkInit() {
         if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
         RuntimeInit.enableDdms();
+        /*
+         * Replace libcore's minimal default mapping between MIME types and file
+         * extensions with a mapping that's suitable for Android. Android's mapping
+         * contains many more entries that are derived from IANA registrations but
+         * with several customizations (extensions, overrides).
+         */
+        MimeMap.setDefault(DefaultMimeMapFactory.create());
     }
 
     @UnsupportedAppUsage
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bf0f10e..1ca9383 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -458,15 +458,6 @@
     transaction->setInputWindowInfo(ctrl, *handle->getInfo());
 }
 
-static void nativeTransferTouchFocus(JNIEnv* env, jclass clazz, jlong transactionObj,
-        jobject fromTokenObj, jobject toTokenObj) {
-    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-
-    sp<IBinder> fromToken(ibinderForJavaObject(env, fromTokenObj));
-    sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
-    transaction->transferTouchFocus(fromToken, toToken);
-}
-
 static void nativeSyncInputWindows(JNIEnv* env, jclass clazz, jlong transactionObj) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     transaction->syncInputWindows();
@@ -1381,8 +1372,6 @@
             (void*)nativeCaptureLayers },
     {"nativeSetInputWindowInfo", "(JJLandroid/view/InputWindowHandle;)V",
             (void*)nativeSetInputWindowInfo },
-    {"nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)V",
-            (void*)nativeTransferTouchFocus },
     {"nativeSetMetadata", "(JJILandroid/os/Parcel;)V",
             (void*)nativeSetMetadata },
     {"nativeGetDisplayedContentSamplingAttributes",
diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml
index f1df2de..37d5a411 100644
--- a/core/res/res/anim/screen_rotate_0_exit.xml
+++ b/core/res/res/anim/screen_rotate_0_exit.xml
@@ -19,7 +19,4 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
-    <alpha android:fromAlpha="1.0" android:toAlpha="0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:duration="@android:integer/config_shortAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index 1eb6361..58a1868 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -25,7 +25,4 @@
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_mediumAnimTime" />
-    <alpha android:fromAlpha="1.0" android:toAlpha="0"
-            android:interpolator="@interpolator/decelerate_cubic"
-            android:duration="@android:integer/config_mediumAnimTime"/>
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_minus_90_frame.xml b/core/res/res/anim/screen_rotate_alpha.xml
similarity index 68%
rename from core/res/res/anim/screen_rotate_minus_90_frame.xml
rename to core/res/res/anim/screen_rotate_alpha.xml
index 2d198f3..c49ef9c 100644
--- a/core/res/res/anim/screen_rotate_minus_90_frame.xml
+++ b/core/res/res/anim/screen_rotate_alpha.xml
@@ -19,10 +19,9 @@
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
-    <rotate android:fromDegrees="0" android:toDegrees="90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+           android:interpolator="@interpolator/decelerate_quint"
+           android:fillEnabled="true"
+           android:fillBefore="true" android:fillAfter="true"
+           android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index 9b38939..0927dd3 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -40,9 +40,4 @@
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_mediumAnimTime" />
-    <alpha android:fromAlpha="1.0" android:toAlpha="0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index fa34533..fd786f9 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -40,9 +40,4 @@
             android:fillEnabled="true"
             android:fillBefore="true" android:fillAfter="true"
             android:duration="@android:integer/config_mediumAnimTime" />
-    <alpha android:fromAlpha="1.0" android:toAlpha="0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_frame.xml b/core/res/res/anim/screen_rotate_plus_90_frame.xml
deleted file mode 100644
index cd20050..0000000
--- a/core/res/res/anim/screen_rotate_plus_90_frame.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <rotate android:fromDegrees="0" android:toDegrees="-90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
-</set>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3d0a3b3..e2f2b2c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1881,15 +1881,14 @@
   <java-symbol type="anim" name="screen_rotate_180_enter" />
   <java-symbol type="anim" name="screen_rotate_180_exit" />
   <java-symbol type="anim" name="screen_rotate_180_frame" />
+  <java-symbol type="anim" name="screen_rotate_alpha"/>
   <java-symbol type="anim" name="screen_rotate_finish_enter" />
   <java-symbol type="anim" name="screen_rotate_finish_exit" />
   <java-symbol type="anim" name="screen_rotate_finish_frame" />
   <java-symbol type="anim" name="screen_rotate_minus_90_enter" />
   <java-symbol type="anim" name="screen_rotate_minus_90_exit" />
-  <java-symbol type="anim" name="screen_rotate_minus_90_frame" />
   <java-symbol type="anim" name="screen_rotate_plus_90_enter" />
   <java-symbol type="anim" name="screen_rotate_plus_90_exit" />
-  <java-symbol type="anim" name="screen_rotate_plus_90_frame" />
   <java-symbol type="anim" name="screen_rotate_start_enter" />
   <java-symbol type="anim" name="screen_rotate_start_exit" />
   <java-symbol type="anim" name="screen_rotate_start_frame" />
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 072beae..c692097 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -323,14 +323,16 @@
         <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
     </family>
     <family lang="und-Mymr" variant="elegant">
-        <font weight="400" style="normal">NotoSansMyanmar-Regular-ZawDecode.ttf</font>
-        <font weight="700" style="normal">NotoSansMyanmar-Bold-ZawDecode.ttf</font>
+        <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
         <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
     </family>
     <family lang="und-Mymr" variant="compact">
-        <font weight="400" style="normal">NotoSansMyanmarUI-Regular-ZawDecode.ttf</font>
-        <font weight="700" style="normal">NotoSansMyanmarUI-Bold-ZawDecode.ttf</font>
+        <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+        <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+        <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
     </family>
     <family lang="und-Thaa">
         <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font>
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index b017384..f839213e 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -43,41 +43,28 @@
     if (!matrix.rectStaysRect()) return true;
     SkRect dstDevRect = matrix.mapRect(dstRect);
     float dstW, dstH;
-    bool requiresIntegerTranslate = false;
     if (MathUtils::isZero(matrix.getScaleX()) && MathUtils::isZero(matrix.getScaleY())) {
         // Has a 90 or 270 degree rotation, although total matrix may also have scale factors
         // in m10 and m01. Those scalings are automatically handled by mapRect so comparing
         // dimensions is sufficient, but swap width and height comparison.
         dstW = dstDevRect.height();
         dstH = dstDevRect.width();
-        requiresIntegerTranslate = true;
     } else {
         // Handle H/V flips or 180 rotation matrices. Axes may have been mirrored, but
         // dimensions are still safe to compare directly.
         dstW = dstDevRect.width();
         dstH = dstDevRect.height();
-        requiresIntegerTranslate =
-                matrix.getScaleX() < -NON_ZERO_EPSILON || matrix.getScaleY() < -NON_ZERO_EPSILON;
     }
     if (!(MathUtils::areEqual(dstW, srcRect.width()) &&
           MathUtils::areEqual(dstH, srcRect.height()))) {
         return true;
     }
-    if (requiresIntegerTranslate) {
-        // Device rect and source rect should be integer aligned to ensure there's no difference
-        // in how nearest-neighbor sampling is resolved.
-        return !(isIntegerAligned(srcRect.x()) &&
-                 isIntegerAligned(srcRect.y()) &&
-                 isIntegerAligned(dstDevRect.x()) &&
-                 isIntegerAligned(dstDevRect.y()));
-    } else {
-        // As long as src and device rects are translated by the same fractional amount,
-        // filtering won't be needed
-        return !(MathUtils::areEqual(SkScalarFraction(srcRect.x()),
-                                     SkScalarFraction(dstDevRect.x())) &&
-                 MathUtils::areEqual(SkScalarFraction(srcRect.y()),
-                                     SkScalarFraction(dstDevRect.y())));
-    }
+    // Device rect and source rect should be integer aligned to ensure there's no difference
+    // in how nearest-neighbor sampling is resolved.
+    return !(isIntegerAligned(srcRect.x()) &&
+             isIntegerAligned(srcRect.y()) &&
+             isIntegerAligned(dstDevRect.x()) &&
+             isIntegerAligned(dstDevRect.y()));
 }
 
 bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer,
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 378064d..6257feb 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -34,6 +34,7 @@
         "libutils",
         "libbinder",
         "libmedia",
+        "libmedia_codeclist",
         "libmedia_jni_utils",
         "libmedia_omx",
         "libmediametrics",
diff --git a/mime/Android.bp b/mime/Android.bp
new file mode 100644
index 0000000..17bad74
--- /dev/null
+++ b/mime/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "mimemap",
+    visibility: [
+        "//cts/tests/tests/mimemap:__subpackages__",
+        "//frameworks/base:__subpackages__",
+    ],
+
+    srcs: [
+        "java/android/content/type/DefaultMimeMapFactory.java",
+    ],
+
+    java_resources: [
+        ":debian.mime.types",
+        ":android.mime.types",
+    ],
+
+    sdk_version: "core_platform",
+}
+
+filegroup {
+    name: "android.mime.types",
+    visibility: [
+        "//visibility:private",
+    ],
+    path: "java-res/",
+    srcs: [
+        "java-res/android.mime.types",
+    ],
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
new file mode 100644
index 0000000..1ca912e
--- /dev/null
+++ b/mime/java-res/android.mime.types
@@ -0,0 +1,146 @@
+
+###############################################################################
+#
+# Android-specific MIME type <-> extension mappings
+#
+# Each line below defines an mapping from one MIME type to the first of the
+# listed extensions, and from listed extension back to the MIME type.
+# A mapping overrides any previous mapping _from_ that same MIME type or
+# extension (put() semantics), unless that MIME type / extension is prefixed with '?'
+# (putIfAbsent() semantics).
+#
+#
+###############################################################################
+#
+# EXAMPLES
+#
+# A line of the form:
+#
+#    ?mime ext1 ?ext2 ext3
+#
+# affects the current mappings along the lines of the following pseudo code:
+#
+#    mimeToExt.putIfAbsent("mime", "ext1");
+#    extToMime.put("ext1", "mime");
+#    extToMime.putIfAbsent("ext2", "mime");
+#    extToMime.put("ext3", "mime");
+#
+# The line:
+#
+#     ?text/plain txt
+#
+# leaves any earlier mapping for "text/plain" untouched, or maps that MIME type
+# to the file extension ".txt" if there is no earlier mapping. The line also
+# sets the mapping from file extension ".txt" to be the MIME type "text/plain",
+# regardless of whether a previous mapping existed.
+#
+###############################################################################
+
+
+# File extensions that Android wants to override to point to the given MIME type.
+#
+# After processing a line of the form:
+# ?<mimeType> <extension1> <extension2>
+# If <mimeType> was not already mapped to an extension then it will be
+# mapped to <extension1>.
+# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
+
+?application/epub+zip epub
+?application/pkix-cert cer
+?application/rss+xml rss
+?application/vnd.android.ota ota
+?application/vnd.apple.mpegurl m3u8
+?application/vnd.ms-pki.stl stl
+?application/vnd.ms-powerpoint pot
+?application/vnd.ms-wpl wpl
+?application/vnd.stardivision.impress sdp
+?application/vnd.stardivision.writer vor
+?application/vnd.youtube.yt yt
+?application/x-android-drm-fl fl
+?application/x-flac flac
+?application/x-font pcf
+?application/x-mpegurl m3u m3u8
+?application/x-pem-file pem
+?application/x-pkcs12 p12 pfx
+?application/x-webarchive webarchive
+?application/x-webarchive-xml webarchivexml
+?application/x-x509-server-cert crt
+?application/x-x509-user-cert crt
+
+?audio/3gpp 3gpp
+?audio/aac-adts aac
+?audio/imelody imy
+?audio/midi rtttl xmf
+?audio/mobile-xmf mxmf
+?audio/mp4 m4a
+?audio/mpegurl m3u
+?audio/sp-midi smf
+?audio/x-matroska mka
+?audio/x-pn-realaudio ra
+
+?image/bmp bmp
+?image/heic heic
+?image/heic-sequence heics
+?image/heif heif hif
+?image/heif-sequence heifs
+?image/ico cur
+?image/webp webp
+?image/x-adobe-dng dng
+?image/x-fuji-raf raf
+?image/x-icon ico
+?image/x-nikon-nrw nrw
+?image/x-panasonic-rw2 rw2
+?image/x-pentax-pef pef
+?image/x-samsung-srw srw
+?image/x-sony-arw arw
+
+?text/comma-separated-values csv
+?text/plain diff po
+?text/rtf rtf
+?text/text phps
+?text/xml xml
+?text/x-vcard vcf
+
+?video/3gpp2 3gpp2 3g2
+?video/3gpp 3gpp
+?video/avi avi
+?video/m4v m4v
+?video/mp2p mpeg
+?video/mp2t m2ts mts
+?video/mp2ts ts
+?video/vnd.youtube.yt yt
+?video/x-webex wrf
+
+# Optional additions that should not override any previous mapping.
+
+?application/x-wifi-config ?xml
+
+# Special cases where Android has a strong opinion about mappings, so we
+# define them very last and make them override in both directions (no "?").
+#
+# Lines here are of the form:
+# <mimeType> <extension1> <extension2> ...
+#
+# After processing each line,
+#   <mimeType> is mapped to <extension1>
+#   <extension1>, <extension2>, ... are all mapped to <mimeType>
+# This overrides any mappings for this <mimeType> / for these extensions
+# that may have been defined earlier.
+
+application/pgp-signature pgp
+application/x-x509-ca-cert crt
+audio/aac aac
+audio/basic snd
+audio/flac flac
+audio/midi rtx
+audio/mpeg mp3 m4a m4r
+audio/x-mpegurl m3u m3u8
+image/jpeg jpg
+image/x-ms-bmp bmp
+text/plain txt
+text/x-c++hdr hpp
+text/x-c++src cpp
+video/3gpp 3gpp
+video/mpeg mpeg
+video/quicktime mov
+video/x-matroska mkv
diff --git a/mime/java/android/content/type/DefaultMimeMapFactory.java b/mime/java/android/content/type/DefaultMimeMapFactory.java
new file mode 100644
index 0000000..545fb3c
--- /dev/null
+++ b/mime/java/android/content/type/DefaultMimeMapFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.type;
+
+import libcore.net.MimeMap;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Creates the framework default {@link MimeMap}, a bidirectional mapping
+ * between MIME types and file extensions.
+ *
+ * This default mapping is loaded from data files that start with some mappings
+ * recognized by IANA plus some custom extensions and overrides.
+ *
+ * @hide
+ */
+public class DefaultMimeMapFactory {
+
+    private DefaultMimeMapFactory() {
+    }
+
+    /**
+     * Creates and returns a new {@link MimeMap} instance that implements.
+     * Android's default mapping between MIME types and extensions.
+     */
+    public static MimeMap create() {
+        return parseFromResources("/mime.types", "/android.mime.types");
+    }
+
+    private static final Pattern SPLIT_PATTERN = Pattern.compile("\\s+");
+
+    static MimeMap parseFromResources(String... resourceNames) {
+        MimeMap.Builder builder = MimeMap.builder();
+        for (String resourceName : resourceNames) {
+            parseTypes(builder, resourceName);
+        }
+        return builder.build();
+    }
+
+    private static void parseTypes(MimeMap.Builder builder, String resource) {
+        try (BufferedReader r = new BufferedReader(
+                new InputStreamReader(DefaultMimeMapFactory.class.getResourceAsStream(resource)))) {
+            String line;
+            while ((line = r.readLine()) != null) {
+                int commentPos = line.indexOf('#');
+                if (commentPos >= 0) {
+                    line = line.substring(0, commentPos);
+                }
+                line = line.trim();
+                if (line.isEmpty()) {
+                    continue;
+                }
+                List<String> specs = Arrays.asList(SPLIT_PATTERN.split(line));
+                builder.put(specs.get(0), specs.subList(1, specs.size()));
+            }
+        } catch (IOException | RuntimeException e) {
+            throw new RuntimeException("Failed to parse " + resource, e);
+        }
+    }
+
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java
new file mode 100644
index 0000000..f3ab2bd
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import android.app.backup.BackupTransport;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Accepts the full backup data stream and sends it to the server. */
+public interface FullBackupDataProcessor {
+    /**
+     * Prepares the upload.
+     *
+     * <p>After this, call {@link #start()} to establish the connection.
+     *
+     * @param inputStream to read the backup data from, calling {@link #finish} or {@link #cancel}
+     *     will close the stream
+     * @return {@code true} if the connection was set up successfully, otherwise {@code false}
+     */
+    boolean initiate(InputStream inputStream) throws IOException;
+
+    /**
+     * Starts the upload, establishing the connection to the server.
+     *
+     * <p>After this, call {@link #pushData(int)} to request that the processor reads data from the
+     * socket, and uploads it to the server.
+     *
+     * <p>After this you must call one of {@link #cancel()}, {@link #finish()}, {@link
+     * #handleCheckSizeRejectionZeroBytes()}, {@link #handleCheckSizeRejectionQuotaExceeded()} or
+     * {@link #handleSendBytesQuotaExceeded()} to close the upload.
+     */
+    void start();
+
+    /**
+     * Requests that the processor read {@code numBytes} from the input stream passed in {@link
+     * #initiate(InputStream)} and upload them to the server.
+     *
+     * @return {@link BackupTransport#TRANSPORT_OK} if the upload succeeds, or {@link
+     *     BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size
+     *     quota, or {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors.
+     */
+    int pushData(int numBytes);
+
+    /** Cancels the upload and tears down the connection. */
+    void cancel();
+
+    /**
+     * Finish the upload and tear down the connection.
+     *
+     * <p>Call this after there is no more data to push with {@link #pushData(int)}.
+     *
+     * @return One of {@link BackupTransport#TRANSPORT_OK} if the app upload succeeds, {@link
+     *     BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size
+     *     quota, {@link BackupTransport#TRANSPORT_ERROR} for server 500s, or {@link
+     *     BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors.
+     */
+    int finish();
+
+    /**
+     * Notifies the processor that the current upload should be terminated because the estimated
+     * size is zero.
+     */
+    void handleCheckSizeRejectionZeroBytes();
+
+    /**
+     * Notifies the processor that the current upload should be terminated because the estimated
+     * size exceeds the quota.
+     */
+    void handleCheckSizeRejectionQuotaExceeded();
+
+    /**
+     * Notifies this class that the current upload should be terminated because the quota was
+     * exceeded during upload.
+     */
+    void handleSendBytesQuotaExceeded();
+
+    /**
+     * Attaches {@link FullBackupCallbacks} which the processor will notify when the backup
+     * succeeds.
+     */
+    void attachCallbacks(FullBackupCallbacks fullBackupCallbacks);
+
+    /**
+     * Implemented by the caller of the processor to receive notification of when the backup
+     * succeeds.
+     */
+    interface FullBackupCallbacks {
+        /** The processor calls this to indicate that the current backup has succeeded. */
+        void onSuccess();
+
+        /** The processor calls this if the upload failed for a non-transient reason. */
+        void onTransferFailed();
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java
new file mode 100644
index 0000000..e4c4049
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 java.io.IOException;
+
+/**
+ * Retrieves the data during a full restore, decrypting it if necessary.
+ *
+ * <p>Use {@link FullRestoreDataProcessorFactory} to construct the encrypted or unencrypted
+ * processor as appropriate during restore.
+ */
+public interface FullRestoreDataProcessor {
+    /** Return value of {@link #readNextChunk} when there is no more data to download. */
+    int END_OF_STREAM = -1;
+
+    /**
+     * Reads the next chunk of restore data and writes it to the given buffer.
+     *
+     * <p>Where necessary, will open the connection to the server and/or decrypt the backup file.
+     *
+     * <p>The implementation may retry various errors. If the retries fail it will throw the
+     * relevant exception.
+     *
+     * @return the number of bytes read, or {@link #END_OF_STREAM} if there is no more data
+     * @throws IOException when downloading from the network or writing to disk
+     */
+    int readNextChunk(byte[] buffer) throws IOException;
+
+    /**
+     * Closes the connection to the server, deletes any temporary files and optionally sends a log
+     * with the given finish type.
+     *
+     * @param finishType one of {@link FullRestoreDownloader.FinishType}
+     */
+    void finish(FullRestoreDownloader.FinishType finishType);
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java
new file mode 100644
index 0000000..91b2926
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.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.server.backup.encryption;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/** Utility methods for dealing with Streams */
+public class StreamUtils {
+    /**
+     * Close a Closeable and silently ignore any IOExceptions.
+     *
+     * @param closeable The closeable to close
+     */
+    public static void closeQuietly(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException ioe) {
+            // Silently ignore
+        }
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java
new file mode 100644
index 0000000..9e31385
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.client;
+
+/**
+ * Error thrown when the user attempts to retrieve a key set from the server, but is asking for keys
+ * from an inactive secondary.
+ *
+ * <p>Although we could just return old keys, there is no good reason to do this. It almost
+ * certainly indicates a logic error on the client.
+ */
+public class UnexpectedActiveSecondaryOnServerException extends Exception {
+    public UnexpectedActiveSecondaryOnServerException(String message) {
+        super(message);
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java
new file mode 100644
index 0000000..2e8a61f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+/**
+ * Error thrown when the server's active secondary key does not exist in the user's recoverable
+ * keychain. This means the backup data cannot be decrypted, and should be wiped.
+ */
+public class ActiveSecondaryNotInKeychainException extends Exception {
+    public ActiveSecondaryNotInKeychainException(String message) {
+        super(message);
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java
new file mode 100644
index 0000000..a938d71
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.content.Context;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.StreamUtils;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+
+import javax.crypto.SecretKey;
+
+/**
+ * Task which reads a stream of plaintext full backup data, chunks it, encrypts it and uploads it to
+ * the server.
+ *
+ * <p>Once the backup completes or fails, closes the input stream.
+ */
+public class EncryptedFullBackupTask implements Callable<Void> {
+    private static final String TAG = "EncryptedFullBackupTask";
+
+    private static final int MIN_CHUNK_SIZE_BYTES = 2 * 1024;
+    private static final int MAX_CHUNK_SIZE_BYTES = 64 * 1024;
+    private static final int AVERAGE_CHUNK_SIZE_BYTES = 4 * 1024;
+
+    // TODO(b/69350270): Remove this hard-coded salt and related logic once we feel confident that
+    // incremental backup has happened at least once for all existing packages/users since we moved
+    // to
+    // using a randomly generated salt.
+    //
+    // The hard-coded fingerprint mixer salt was used for a short time period before replaced by one
+    // that is randomly generated on initial non-incremental backup and stored in ChunkListing to be
+    // reused for succeeding incremental backups. If an old ChunkListing does not have a
+    // fingerprint_mixer_salt, we assume that it was last backed up before a randomly generated salt
+    // is used so we use the hardcoded salt and set ChunkListing#fingerprint_mixer_salt to this
+    // value.
+    // Eventually all backup ChunkListings will have this field set and then we can remove the
+    // default
+    // value in the code.
+    static final byte[] DEFAULT_FINGERPRINT_MIXER_SALT =
+            Arrays.copyOf(new byte[] {20, 23}, FingerprintMixer.SALT_LENGTH_BYTES);
+
+    private final ProtoStore<ChunkListing> mChunkListingStore;
+    private final TertiaryKeyManager mTertiaryKeyManager;
+    private final InputStream mInputStream;
+    private final EncryptedBackupTask mTask;
+    private final String mPackageName;
+    private final SecureRandom mSecureRandom;
+
+    /** Creates a new instance with the default min, max and average chunk sizes. */
+    public static EncryptedFullBackupTask newInstance(
+            Context context,
+            CryptoBackupServer cryptoBackupServer,
+            SecureRandom secureRandom,
+            RecoverableKeyStoreSecondaryKey secondaryKey,
+            String packageName,
+            InputStream inputStream)
+            throws IOException {
+        EncryptedBackupTask encryptedBackupTask =
+                new EncryptedBackupTask(
+                        cryptoBackupServer,
+                        secureRandom,
+                        packageName,
+                        new BackupStreamEncrypter(
+                                inputStream,
+                                MIN_CHUNK_SIZE_BYTES,
+                                MAX_CHUNK_SIZE_BYTES,
+                                AVERAGE_CHUNK_SIZE_BYTES));
+        TertiaryKeyManager tertiaryKeyManager =
+                new TertiaryKeyManager(
+                        context,
+                        secureRandom,
+                        TertiaryKeyRotationScheduler.getInstance(context),
+                        secondaryKey,
+                        packageName);
+
+        return new EncryptedFullBackupTask(
+                ProtoStore.createChunkListingStore(context),
+                tertiaryKeyManager,
+                encryptedBackupTask,
+                inputStream,
+                packageName,
+                new SecureRandom());
+    }
+
+    @VisibleForTesting
+    EncryptedFullBackupTask(
+            ProtoStore<ChunkListing> chunkListingStore,
+            TertiaryKeyManager tertiaryKeyManager,
+            EncryptedBackupTask task,
+            InputStream inputStream,
+            String packageName,
+            SecureRandom secureRandom) {
+        mChunkListingStore = chunkListingStore;
+        mTertiaryKeyManager = tertiaryKeyManager;
+        mInputStream = inputStream;
+        mTask = task;
+        mPackageName = packageName;
+        mSecureRandom = secureRandom;
+    }
+
+    @Override
+    public Void call() throws Exception {
+        try {
+            Optional<ChunkListing> maybeOldChunkListing =
+                    mChunkListingStore.loadProto(mPackageName);
+
+            if (maybeOldChunkListing.isPresent()) {
+                Slog.i(TAG, "Found previous chunk listing for " + mPackageName);
+            }
+
+            // If the key has been rotated then we must re-encrypt all of the backup data.
+            if (mTertiaryKeyManager.wasKeyRotated()) {
+                Slog.i(
+                        TAG,
+                        "Key was rotated or newly generated for "
+                                + mPackageName
+                                + ", so performing a full backup.");
+                maybeOldChunkListing = Optional.empty();
+                mChunkListingStore.deleteProto(mPackageName);
+            }
+
+            SecretKey tertiaryKey = mTertiaryKeyManager.getKey();
+            WrappedKeyProto.WrappedKey wrappedTertiaryKey = mTertiaryKeyManager.getWrappedKey();
+
+            ChunkListing newChunkListing;
+            if (!maybeOldChunkListing.isPresent()) {
+                byte[] fingerprintMixerSalt = new byte[FingerprintMixer.SALT_LENGTH_BYTES];
+                mSecureRandom.nextBytes(fingerprintMixerSalt);
+                newChunkListing =
+                        mTask.performNonIncrementalBackup(
+                                tertiaryKey, wrappedTertiaryKey, fingerprintMixerSalt);
+            } else {
+                ChunkListing oldChunkListing = maybeOldChunkListing.get();
+
+                if (oldChunkListing.fingerprintMixerSalt == null
+                        || oldChunkListing.fingerprintMixerSalt.length == 0) {
+                    oldChunkListing.fingerprintMixerSalt = DEFAULT_FINGERPRINT_MIXER_SALT;
+                }
+
+                newChunkListing =
+                        mTask.performIncrementalBackup(
+                                tertiaryKey, wrappedTertiaryKey, oldChunkListing);
+            }
+
+            mChunkListingStore.saveProto(mPackageName, newChunkListing);
+            Slog.v(TAG, "Saved chunk listing for " + mPackageName);
+        } catch (IOException e) {
+            Slog.e(TAG, "Storage exception, wiping state");
+            mChunkListingStore.deleteProto(mPackageName);
+            throw e;
+        } finally {
+            StreamUtils.closeQuietly(mInputStream);
+        }
+
+        return null;
+    }
+
+    /**
+     * Signals to the task that the backup has been cancelled. If the upload has not yet started
+     * then the task will not upload any data to the server or save the new chunk listing.
+     *
+     * <p>You must then terminate the input stream.
+     */
+    public void cancel() {
+        mTask.cancel();
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java
new file mode 100644
index 0000000..04381af
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.FullRestoreDataProcessor;
+import com.android.server.backup.encryption.FullRestoreDownloader;
+import com.android.server.backup.encryption.StreamUtils;
+import com.android.server.backup.encryption.chunking.DecryptedChunkFileOutput;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+
+/** Downloads the encrypted backup file, decrypts it and passes the data to backup manager. */
+public class EncryptedFullRestoreTask implements FullRestoreDataProcessor {
+    private static final String DEFAULT_TEMPORARY_FOLDER = "encrypted_restore_temp";
+    private static final String ENCRYPTED_FILE_NAME = "encrypted_restore";
+    private static final String DECRYPTED_FILE_NAME = "decrypted_restore";
+
+    private final FullRestoreToFileTask mFullRestoreToFileTask;
+    private final BackupFileDecryptorTask mBackupFileDecryptorTask;
+    private final File mEncryptedFile;
+    private final File mDecryptedFile;
+    @Nullable private InputStream mDecryptedFileInputStream;
+
+    /**
+     * Creates a new task which stores temporary files in the files directory.
+     *
+     * @param fullRestoreDownloader which will download the backup file
+     * @param tertiaryKey which the backup file is encrypted with
+     */
+    public static EncryptedFullRestoreTask newInstance(
+            Context context, FullRestoreDownloader fullRestoreDownloader, SecretKey tertiaryKey)
+            throws NoSuchAlgorithmException, NoSuchPaddingException {
+        File temporaryFolder = new File(context.getFilesDir(), DEFAULT_TEMPORARY_FOLDER);
+        temporaryFolder.mkdirs();
+        return new EncryptedFullRestoreTask(
+                temporaryFolder, fullRestoreDownloader, new BackupFileDecryptorTask(tertiaryKey));
+    }
+
+    @VisibleForTesting
+    EncryptedFullRestoreTask(
+            File temporaryFolder,
+            FullRestoreDownloader fullRestoreDownloader,
+            BackupFileDecryptorTask backupFileDecryptorTask) {
+        checkArgument(temporaryFolder.isDirectory(), "Temporary folder must be existing directory");
+
+        mEncryptedFile = new File(temporaryFolder, ENCRYPTED_FILE_NAME);
+        mDecryptedFile = new File(temporaryFolder, DECRYPTED_FILE_NAME);
+
+        mFullRestoreToFileTask = new FullRestoreToFileTask(fullRestoreDownloader);
+        mBackupFileDecryptorTask = backupFileDecryptorTask;
+    }
+
+    /**
+     * Reads the next decrypted bytes into the given buffer.
+     *
+     * <p>During the first call this method will download the backup file from the server, decrypt
+     * it and save it to disk. It will then read the bytes from the file on disk.
+     *
+     * <p>Once this method has read all the bytes of the file, the caller must call {@link #finish}
+     * to clean up.
+     *
+     * @return the number of bytes read, or {@code -1} on reaching the end of the file
+     */
+    @Override
+    public int readNextChunk(byte[] buffer) throws IOException {
+        if (mDecryptedFileInputStream == null) {
+            try {
+                mDecryptedFileInputStream = downloadAndDecryptBackup();
+            } catch (BadPaddingException
+                    | InvalidKeyException
+                    | NoSuchAlgorithmException
+                    | IllegalBlockSizeException
+                    | ShortBufferException
+                    | EncryptedRestoreException
+                    | InvalidAlgorithmParameterException e) {
+                throw new IOException("Encryption issue", e);
+            }
+        }
+
+        return mDecryptedFileInputStream.read(buffer);
+    }
+
+    private InputStream downloadAndDecryptBackup()
+            throws IOException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
+                    IllegalBlockSizeException, ShortBufferException, EncryptedRestoreException,
+                    InvalidAlgorithmParameterException {
+        mFullRestoreToFileTask.restoreToFile(mEncryptedFile);
+        mBackupFileDecryptorTask.decryptFile(
+                mEncryptedFile, new DecryptedChunkFileOutput(mDecryptedFile));
+        mEncryptedFile.delete();
+        return new BufferedInputStream(new FileInputStream(mDecryptedFile));
+    }
+
+    /** Cleans up temporary files. */
+    @Override
+    public void finish(FullRestoreDownloader.FinishType unusedFinishType) {
+        // The download is finished and log sent during RestoreToFileTask#restoreToFile(), so we
+        // don't need to do either of those things here.
+
+        StreamUtils.closeQuietly(mDecryptedFileInputStream);
+        mEncryptedFile.delete();
+        mDecryptedFile.delete();
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java
new file mode 100644
index 0000000..72e8a89
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * Error thrown if attempting to rotate key when there is no current active secondary key set
+ * locally. This means the device needs to re-initialize, asking the backup server what the active
+ * secondary key is.
+ */
+public class NoActiveSecondaryKeyException extends Exception {
+    public NoActiveSecondaryKeyException(String message) {
+        super(message);
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
new file mode 100644
index 0000000..d58cb66
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 android.os.Build.VERSION_CODES.P;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyStore;
+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.UnrecoverableKeyException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Finishes a rotation for a {@link
+ * com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey}.
+ */
+public class RotateSecondaryKeyTask {
+    private static final String TAG = "RotateSecondaryKeyTask";
+
+    private final Context mContext;
+    private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+    private final CryptoBackupServer mBackupServer;
+    private final CryptoSettings mCryptoSettings;
+    private final RecoveryController mRecoveryController;
+
+    /**
+     * A new instance.
+     *
+     * @param secondaryKeyManager For loading the currently active and next secondary key.
+     * @param backupServer For loading and storing tertiary keys and for setting active secondary
+     *     key.
+     * @param cryptoSettings For checking the stored aliases for the next and active key.
+     * @param recoveryController For communicating with the Framework apis.
+     */
+    public RotateSecondaryKeyTask(
+            Context context,
+            RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager,
+            CryptoBackupServer backupServer,
+            CryptoSettings cryptoSettings,
+            RecoveryController recoveryController) {
+        mContext = context;
+        mSecondaryKeyManager = checkNotNull(secondaryKeyManager);
+        mCryptoSettings = checkNotNull(cryptoSettings);
+        mBackupServer = checkNotNull(backupServer);
+        mRecoveryController = checkNotNull(recoveryController);
+    }
+
+    /** Runs the task. */
+    public void run() {
+        // Never run more than one of these at the same time.
+        synchronized (RotateSecondaryKeyTask.class) {
+            runInternal();
+        }
+    }
+
+    private void runInternal() {
+        Optional<RecoverableKeyStoreSecondaryKey> maybeNextKey;
+        try {
+            maybeNextKey = getNextKey();
+        } catch (Exception e) {
+            Slog.e(TAG, "Error checking for next key", e);
+            return;
+        }
+
+        if (!maybeNextKey.isPresent()) {
+            Slog.d(TAG, "No secondary key rotation task pending. Exiting.");
+            return;
+        }
+
+        RecoverableKeyStoreSecondaryKey nextKey = maybeNextKey.get();
+        boolean isReady;
+        try {
+            isReady = isSecondaryKeyRotationReady(nextKey);
+        } catch (InternalRecoveryServiceException e) {
+            Slog.e(TAG, "Error encountered checking whether next secondary key is synced", e);
+            return;
+        }
+
+        if (!isReady) {
+            return;
+        }
+
+        try {
+            rotateToKey(nextKey);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error trying to rotate to new secondary key", e);
+        }
+    }
+
+    private Optional<RecoverableKeyStoreSecondaryKey> getNextKey()
+            throws InternalRecoveryServiceException, UnrecoverableKeyException {
+        Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias();
+        if (!maybeNextAlias.isPresent()) {
+            return Optional.empty();
+        }
+        return mSecondaryKeyManager.get(maybeNextAlias.get());
+    }
+
+    private boolean isSecondaryKeyRotationReady(RecoverableKeyStoreSecondaryKey nextKey)
+            throws InternalRecoveryServiceException {
+        String nextAlias = nextKey.getAlias();
+        Slog.i(TAG, "Key rotation to " + nextAlias + " is pending. Checking key sync status.");
+        int status = mRecoveryController.getRecoveryStatus(nextAlias);
+
+        if (status == RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE) {
+            Slog.e(
+                    TAG,
+                    "Permanent failure to sync " + nextAlias + ". Cannot possibly rotate to it.");
+            mCryptoSettings.removeNextSecondaryKeyAlias();
+            return false;
+        }
+
+        if (status == RecoveryController.RECOVERY_STATUS_SYNCED) {
+            Slog.i(TAG, "Secondary key " + nextAlias + " has now synced! Commencing rotation.");
+        } else {
+            Slog.i(TAG, "Sync still pending for " + nextAlias);
+        }
+        return status == RecoveryController.RECOVERY_STATUS_SYNCED;
+    }
+
+    /**
+     * @throws ActiveSecondaryNotInKeychainException if the currently active secondary key is not in
+     *     the keychain.
+     * @throws IOException if there is an IO issue communicating with the server or loading from
+     *     disk.
+     * @throws NoActiveSecondaryKeyException if there is no active key set.
+     * @throws IllegalBlockSizeException if there is an issue decrypting a tertiary key.
+     * @throws InvalidKeyException if any of the secondary keys cannot be used for wrapping or
+     *     unwrapping tertiary keys.
+     */
+    private void rotateToKey(RecoverableKeyStoreSecondaryKey newSecondaryKey)
+            throws ActiveSecondaryNotInKeychainException, IOException,
+                    NoActiveSecondaryKeyException, IllegalBlockSizeException, InvalidKeyException,
+                    InternalRecoveryServiceException, UnrecoverableKeyException,
+                    InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+                    NoSuchPaddingException {
+        RecoverableKeyStoreSecondaryKey activeSecondaryKey = getActiveSecondaryKey();
+        String activeSecondaryKeyAlias = activeSecondaryKey.getAlias();
+        String newSecondaryKeyAlias = newSecondaryKey.getAlias();
+        if (newSecondaryKeyAlias.equals(activeSecondaryKeyAlias)) {
+            Slog.i(TAG, activeSecondaryKeyAlias + " was already the active alias.");
+            return;
+        }
+
+        TertiaryKeyStore tertiaryKeyStore =
+                TertiaryKeyStore.newInstance(mContext, activeSecondaryKey);
+        Map<String, SecretKey> tertiaryKeys = tertiaryKeyStore.getAll();
+
+        if (tertiaryKeys.isEmpty()) {
+            Slog.i(
+                    TAG,
+                    "No tertiary keys for " + activeSecondaryKeyAlias + ". No need to rewrap. ");
+            mBackupServer.setActiveSecondaryKeyAlias(
+                    newSecondaryKeyAlias, /*tertiaryKeys=*/ Collections.emptyMap());
+        } else {
+            Map<String, WrappedKeyProto.WrappedKey> rewrappedTertiaryKeys =
+                    rewrapAll(newSecondaryKey, tertiaryKeys);
+            TertiaryKeyStore.newInstance(mContext, newSecondaryKey).putAll(rewrappedTertiaryKeys);
+            Slog.i(
+                    TAG,
+                    "Successfully rewrapped " + rewrappedTertiaryKeys.size() + " tertiary keys");
+            mBackupServer.setActiveSecondaryKeyAlias(newSecondaryKeyAlias, rewrappedTertiaryKeys);
+            Slog.i(
+                    TAG,
+                    "Successfully uploaded new set of tertiary keys to "
+                            + newSecondaryKeyAlias
+                            + " alias");
+        }
+
+        mCryptoSettings.setActiveSecondaryKeyAlias(newSecondaryKeyAlias);
+        mCryptoSettings.removeNextSecondaryKeyAlias();
+        try {
+            mRecoveryController.removeKey(activeSecondaryKeyAlias);
+        } catch (InternalRecoveryServiceException e) {
+            Slog.e(TAG, "Error removing old secondary key from RecoverableKeyStoreLoader", e);
+        }
+    }
+
+    private RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
+            throws NoActiveSecondaryKeyException, ActiveSecondaryNotInKeychainException,
+                    InternalRecoveryServiceException, UnrecoverableKeyException {
+
+        Optional<String> activeSecondaryAlias = mCryptoSettings.getActiveSecondaryKeyAlias();
+
+        if (!activeSecondaryAlias.isPresent()) {
+            Slog.i(
+                    TAG,
+                    "Was asked to rotate secondary key, but local config did not have a secondary "
+                            + "key alias set.");
+            throw new NoActiveSecondaryKeyException("No local active secondary key set.");
+        }
+
+        String activeSecondaryKeyAlias = activeSecondaryAlias.get();
+        Optional<RecoverableKeyStoreSecondaryKey> secondaryKey =
+                mSecondaryKeyManager.get(activeSecondaryKeyAlias);
+
+        if (!secondaryKey.isPresent()) {
+            throw new ActiveSecondaryNotInKeychainException(
+                    String.format(
+                            Locale.US,
+                            "Had local active recoverable key alias of %s but key was not in"
+                                + " user's keychain.",
+                            activeSecondaryKeyAlias));
+        }
+
+        return secondaryKey.get();
+    }
+
+    /**
+     * Rewraps all the tertiary keys.
+     *
+     * @param newSecondaryKey The secondary key with which to rewrap the tertiaries.
+     * @param tertiaryKeys The tertiary keys, by package name.
+     * @return The newly wrapped tertiary keys, by package name.
+     * @throws InvalidKeyException if any key is unusable.
+     * @throws IllegalBlockSizeException if could not decrypt.
+     */
+    private Map<String, WrappedKeyProto.WrappedKey> rewrapAll(
+            RecoverableKeyStoreSecondaryKey newSecondaryKey, Map<String, SecretKey> tertiaryKeys)
+            throws InvalidKeyException, IllegalBlockSizeException, NoSuchPaddingException,
+                    NoSuchAlgorithmException {
+        Map<String, WrappedKeyProto.WrappedKey> wrappedKeys = new HashMap<>();
+
+        for (String packageName : tertiaryKeys.keySet()) {
+            SecretKey tertiaryKey = tertiaryKeys.get(packageName);
+            wrappedKeys.put(
+                    packageName, KeyWrapUtils.wrap(newSecondaryKey.getSecretKey(), tertiaryKey));
+        }
+
+        return wrappedKeys;
+    }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java
new file mode 100644
index 0000000..515db86
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+/** Exception thrown when aa backup has exceeded the space allowed for that user */
+public class SizeQuotaExceededException extends RuntimeException {
+    public SizeQuotaExceededException() {
+        super("Backup size quota exceeded.");
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric-integration/Android.bp b/packages/BackupEncryption/test/robolectric-integration/Android.bp
new file mode 100644
index 0000000..f696278
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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: "BackupEncryptionRoboIntegTests",
+    srcs: [
+        "src/**/*.java",
+    ],
+    java_resource_dirs: ["config"],
+    libs: [
+        "backup-encryption-protos",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.rules",
+    ],
+    instrumentation_for: "BackupEncryption",
+}
diff --git a/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml
new file mode 100644
index 0000000..c3930cc
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/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.robointeg">
+
+    <application/>
+
+</manifest>
diff --git a/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties
new file mode 100644
index 0000000..26fceb3
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/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-integration/src/com/android/server/backup/encryption/RoundTripTest.java b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
new file mode 100644
index 0000000..8ec68fd
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.tasks.EncryptedFullBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedFullRestoreTask;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+@RunWith(RobolectricTestRunner.class)
+public class RoundTripTest {
+    /** Amount of data we want to round trip in this test */
+    private static final int TEST_DATA_SIZE = 1024 * 1024; // 1MB
+
+    /** Buffer size used when reading data from the restore task */
+    private static final int READ_BUFFER_SIZE = 1024; // 1024 byte buffer.
+
+    /** Key parameters used for the secondary encryption key */
+    private static final String KEY_ALGORITHM = "AES";
+    private static final int KEY_SIZE_BITS = 256;
+
+    /** Package name for our test package */
+    private static final String TEST_PACKAGE_NAME = "com.android.backup.test";
+
+    /** The name we use to refer to our secondary key */
+    private static final String TEST_KEY_ALIAS = "test/backup/KEY_ALIAS";
+
+    /** Original data used for comparison after round trip */
+    private final byte[] mOriginalData = new byte[TEST_DATA_SIZE];
+
+    /** App context, used to store the key data and chunk listings */
+    private Context mContext;
+
+    /** The secondary key we're using for the test */
+    private RecoverableKeyStoreSecondaryKey mSecondaryKey;
+
+    /** Source of random material which is considered non-predictable in its' generation */
+    private SecureRandom mSecureRandom = new SecureRandom();
+
+    @Before
+    public void setUp() throws NoSuchAlgorithmException {
+        mContext = ApplicationProvider.getApplicationContext();
+        mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_KEY_ALIAS, generateAesKey());
+        fillBuffer(mOriginalData);
+    }
+
+    @Test
+    public void testRoundTrip() throws Exception {
+        byte[] backupData = performBackup(mOriginalData);
+        assertThat(backupData).isNotEqualTo(mOriginalData);
+        byte[] restoredData = performRestore(backupData);
+        assertThat(restoredData).isEqualTo(mOriginalData);
+    }
+
+    /** Perform a backup and return the backed-up representation of the data */
+    private byte[] performBackup(byte[] backupData) throws Exception {
+        DummyServer dummyServer = new DummyServer();
+        EncryptedFullBackupTask backupTask =
+                EncryptedFullBackupTask.newInstance(
+                        mContext,
+                        dummyServer,
+                        mSecureRandom,
+                        mSecondaryKey,
+                        TEST_PACKAGE_NAME,
+                        new ByteArrayInputStream(backupData));
+        backupTask.call();
+        return dummyServer.mStoredData;
+    }
+
+    /** Perform a restore and resturn the bytes obtained from the restore process */
+    private byte[] performRestore(byte[] backupData)
+            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
+                    InvalidAlgorithmParameterException, InvalidKeyException,
+                    IllegalBlockSizeException {
+        ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream();
+
+        EncryptedFullRestoreTask restoreTask =
+                EncryptedFullRestoreTask.newInstance(
+                        mContext, new FakeFullRestoreDownloader(backupData), getTertiaryKey());
+
+        byte[] buffer = new byte[READ_BUFFER_SIZE];
+        int bytesRead = restoreTask.readNextChunk(buffer);
+        while (bytesRead != -1) {
+            decryptedOutput.write(buffer, 0, bytesRead);
+            bytesRead = restoreTask.readNextChunk(buffer);
+        }
+
+        return decryptedOutput.toByteArray();
+    }
+
+    /** Get the tertiary key for our test package from the key manager */
+    private SecretKey getTertiaryKey()
+            throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
+                    NoSuchAlgorithmException, IOException, NoSuchPaddingException,
+                    InvalidKeyException {
+        TertiaryKeyManager tertiaryKeyManager =
+                new TertiaryKeyManager(
+                        mContext,
+                        mSecureRandom,
+                        TertiaryKeyRotationScheduler.getInstance(mContext),
+                        mSecondaryKey,
+                        TEST_PACKAGE_NAME);
+        return tertiaryKeyManager.getKey();
+    }
+
+    /** Fill a buffer with data in a predictable way */
+    private void fillBuffer(byte[] buffer) {
+        byte loopingCounter = 0;
+        for (int i = 0; i < buffer.length; i++) {
+            buffer[i] = loopingCounter;
+            loopingCounter++;
+        }
+    }
+
+    /** Generate a new, random, AES key */
+    public static SecretKey generateAesKey() throws NoSuchAlgorithmException {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
+        keyGenerator.init(KEY_SIZE_BITS);
+        return keyGenerator.generateKey();
+    }
+
+    /**
+     * Dummy backup data endpoint. This stores the data so we can use it
+     * in subsequent test steps.
+     */
+    private static class DummyServer implements CryptoBackupServer {
+        private static final String DUMMY_DOC_ID = "DummyDoc";
+
+        byte[] mStoredData = null;
+
+        @Override
+        public String uploadIncrementalBackup(
+                String packageName,
+                String oldDocId,
+                byte[] diffScript,
+                WrappedKeyProto.WrappedKey tertiaryKey) {
+            throw new RuntimeException("Not Implemented");
+        }
+
+        @Override
+        public String uploadNonIncrementalBackup(
+                String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+            assertThat(packageName).isEqualTo(TEST_PACKAGE_NAME);
+            mStoredData = data;
+            return DUMMY_DOC_ID;
+        }
+
+        @Override
+        public void setActiveSecondaryKeyAlias(
+                String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+            throw new RuntimeException("Not Implemented");
+        }
+    }
+
+    /** Fake package wrapper which returns data from a byte array. */
+    private static class FakeFullRestoreDownloader extends FullRestoreDownloader {
+        private final ByteArrayInputStream mData;
+
+        FakeFullRestoreDownloader(byte[] data) {
+            // We override all methods of the superclass, so it does not require any collaborators.
+            super();
+            mData = new ByteArrayInputStream(data);
+        }
+
+        @Override
+        public int readNextChunk(byte[] buffer) throws IOException {
+            return mData.read(buffer);
+        }
+
+        @Override
+        public void finish(FinishType finishType) {
+            // Do nothing.
+        }
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java
new file mode 100644
index 0000000..096b2da
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import com.android.server.backup.encryption.chunk.ChunkHash;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey;
+import com.android.server.backup.testing.CryptoTestUtils;
+
+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.annotation.Config;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Optional;
+
+import javax.crypto.SecretKey;
+
+@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class})
+@RunWith(RobolectricTestRunner.class)
+public class EncryptedFullBackupTaskTest {
+    private static final String TEST_PACKAGE_NAME = "com.example.package";
+    private static final byte[] TEST_EXISTING_FINGERPRINT_MIXER_SALT =
+            Arrays.copyOf(new byte[] {11}, ChunkHash.HASH_LENGTH_BYTES);
+    private static final byte[] TEST_GENERATED_FINGERPRINT_MIXER_SALT =
+            Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES);
+    private static final ChunkHash TEST_CHUNK_HASH_1 =
+            new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final ChunkHash TEST_CHUNK_HASH_2 =
+            new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES));
+    private static final int TEST_CHUNK_LENGTH_1 = 20;
+    private static final int TEST_CHUNK_LENGTH_2 = 40;
+
+    @Mock private ProtoStore<ChunkListing> mChunkListingStore;
+    @Mock private TertiaryKeyManager mTertiaryKeyManager;
+    @Mock private InputStream mInputStream;
+    @Mock private EncryptedBackupTask mEncryptedBackupTask;
+    @Mock private SecretKey mTertiaryKey;
+    @Mock private SecureRandom mSecureRandom;
+
+    private EncryptedFullBackupTask mTask;
+    private ChunkListing mOldChunkListing;
+    private ChunkListing mNewChunkListing;
+    private WrappedKey mWrappedTertiaryKey;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mWrappedTertiaryKey = new WrappedKey();
+        when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey);
+        when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey);
+
+        mOldChunkListing =
+                CryptoTestUtils.newChunkListing(
+                        /* docId */ null,
+                        TEST_EXISTING_FINGERPRINT_MIXER_SALT,
+                        ChunksMetadataProto.AES_256_GCM,
+                        ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
+                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1));
+        mNewChunkListing =
+                CryptoTestUtils.newChunkListing(
+                        /* docId */ null,
+                        /* fingerprintSalt */ null,
+                        ChunksMetadataProto.AES_256_GCM,
+                        ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED,
+                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1),
+                        CryptoTestUtils.newChunk(TEST_CHUNK_HASH_2.getHash(), TEST_CHUNK_LENGTH_2));
+        when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
+                .thenReturn(mNewChunkListing);
+        when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
+                .thenReturn(mNewChunkListing);
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+
+        doAnswer(invocation -> {
+            byte[] byteArray = (byte[]) invocation.getArguments()[0];
+            System.arraycopy(
+                    TEST_GENERATED_FINGERPRINT_MIXER_SALT,
+                    /* srcPos */ 0,
+                    byteArray,
+                    /* destPos */ 0,
+                    FingerprintMixer.SALT_LENGTH_BYTES);
+            return null;
+        })
+                .when(mSecureRandom)
+                .nextBytes(any(byte[].class));
+
+        mTask =
+                new EncryptedFullBackupTask(
+                        mChunkListingStore,
+                        mTertiaryKeyManager,
+                        mEncryptedBackupTask,
+                        mInputStream,
+                        TEST_PACKAGE_NAME,
+                        mSecureRandom);
+    }
+
+    @Test
+    public void call_existingChunkListingButTertiaryKeyRotated_performsNonIncrementalBackup()
+            throws Exception {
+        when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true);
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+                .thenReturn(Optional.of(mOldChunkListing));
+
+        mTask.call();
+
+        verify(mEncryptedBackupTask)
+                .performNonIncrementalBackup(
+                        eq(mTertiaryKey),
+                        eq(mWrappedTertiaryKey),
+                        eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
+    }
+
+    @Test
+    public void call_noExistingChunkListing_performsNonIncrementalBackup() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+        mTask.call();
+        verify(mEncryptedBackupTask)
+                .performNonIncrementalBackup(
+                        eq(mTertiaryKey),
+                        eq(mWrappedTertiaryKey),
+                        eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT));
+    }
+
+    @Test
+    public void call_existingChunkListing_performsIncrementalBackup() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+                .thenReturn(Optional.of(mOldChunkListing));
+        mTask.call();
+        verify(mEncryptedBackupTask)
+                .performIncrementalBackup(
+                        eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
+    }
+
+    @Test
+    public void
+            call_existingChunkListingWithNoFingerprintMixerSalt_doesntSetSaltBeforeIncBackup()
+                    throws Exception {
+        mOldChunkListing.fingerprintMixerSalt = new byte[0];
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+                .thenReturn(Optional.of(mOldChunkListing));
+
+        mTask.call();
+
+        verify(mEncryptedBackupTask)
+                .performIncrementalBackup(
+                        eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing));
+    }
+
+    @Test
+    public void call_noExistingChunkListing_storesNewChunkListing() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+        mTask.call();
+        verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
+    }
+
+    @Test
+    public void call_existingChunkListing_storesNewChunkListing() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+                .thenReturn(Optional.of(mOldChunkListing));
+        mTask.call();
+        verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing);
+    }
+
+    @Test
+    public void call_exceptionDuringBackup_doesNotSaveNewChunkListing() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty());
+        when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any()))
+                .thenThrow(GeneralSecurityException.class);
+
+        assertThrows(Exception.class, () -> mTask.call());
+
+        assertThat(mChunkListingStore.loadProto(TEST_PACKAGE_NAME).isPresent()).isFalse();
+    }
+
+    @Test
+    public void call_incrementalThrowsPermanentException_clearsState() throws Exception {
+        when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME))
+                .thenReturn(Optional.of(mOldChunkListing));
+        when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any()))
+                .thenThrow(IOException.class);
+
+        assertThrows(IOException.class, () -> mTask.call());
+
+        verify(mChunkListingStore).deleteProto(TEST_PACKAGE_NAME);
+    }
+
+    @Test
+    public void call_closesInputStream() throws Exception {
+        mTask.call();
+        verify(mInputStream).close();
+    }
+
+    @Test
+    public void cancel_cancelsTask() throws Exception {
+        mTask.cancel();
+        verify(mEncryptedBackupTask).cancel();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java
new file mode 100644
index 0000000..0affacd
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+
+import static java.util.stream.Collectors.toList;
+
+import com.android.server.backup.encryption.FullRestoreDownloader;
+
+import com.google.common.io.Files;
+import com.google.common.primitives.Bytes;
+
+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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(RobolectricTestRunner.class)
+public class EncryptedFullRestoreTaskTest {
+    private static final int TEST_BUFFER_SIZE = 10;
+    private static final byte[] TEST_ENCRYPTED_DATA = {1, 2, 3, 4, 5, 6};
+    private static final byte[] TEST_DECRYPTED_DATA = fakeDecrypt(TEST_ENCRYPTED_DATA);
+
+    @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    @Mock private BackupFileDecryptorTask mDecryptorTask;
+
+    private File mFolder;
+    private FakeFullRestoreDownloader mFullRestorePackageWrapper;
+    private EncryptedFullRestoreTask mTask;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mFolder = temporaryFolder.newFolder();
+        mFullRestorePackageWrapper = new FakeFullRestoreDownloader(TEST_ENCRYPTED_DATA);
+
+        doAnswer(
+            invocation -> {
+                File source = invocation.getArgument(0);
+                DecryptedChunkOutput target = invocation.getArgument(1);
+                byte[] decrypted = fakeDecrypt(Files.toByteArray(source));
+                target.open();
+                target.processChunk(decrypted, decrypted.length);
+                target.close();
+                return null;
+            })
+                .when(mDecryptorTask)
+                .decryptFile(any(), any());
+
+        mTask = new EncryptedFullRestoreTask(mFolder, mFullRestorePackageWrapper, mDecryptorTask);
+    }
+
+    @Test
+    public void readNextChunk_downloadsAndDecryptsBackup() throws Exception {
+        ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream();
+
+        byte[] buffer = new byte[TEST_BUFFER_SIZE];
+        int bytesRead = mTask.readNextChunk(buffer);
+        while (bytesRead != -1) {
+            decryptedOutput.write(buffer, 0, bytesRead);
+            bytesRead = mTask.readNextChunk(buffer);
+        }
+
+        assertThat(decryptedOutput.toByteArray()).isEqualTo(TEST_DECRYPTED_DATA);
+    }
+
+    @Test
+    public void finish_deletesTemporaryFiles() throws Exception {
+        mTask.readNextChunk(new byte[10]);
+        mTask.finish(FullRestoreDownloader.FinishType.UNKNOWN_FINISH);
+
+        assertThat(mFolder.listFiles()).isEmpty();
+    }
+
+    /** Fake package wrapper which returns data from a byte array. */
+    private static class FakeFullRestoreDownloader extends FullRestoreDownloader {
+        private final ByteArrayInputStream mData;
+
+        FakeFullRestoreDownloader(byte[] data) {
+            // We override all methods of the superclass, so it does not require any collaborators.
+            super();
+            mData = new ByteArrayInputStream(data);
+        }
+
+        @Override
+        public int readNextChunk(byte[] buffer) throws IOException {
+            return mData.read(buffer);
+        }
+
+        @Override
+        public void finish(FinishType finishType) {
+            // Nothing to do.
+        }
+    }
+
+    /** Fake decrypts a byte array by subtracting 1 from each byte. */
+    private static byte[] fakeDecrypt(byte[] input) {
+        return Bytes.toArray(Bytes.asList(input).stream().map(b -> b + 1).collect(toList()));
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java
new file mode 100644
index 0000000..cda7317
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertFalse;
+
+import android.app.Application;
+import android.platform.test.annotations.Presubmit;
+import android.security.keystore.recovery.RecoveryController;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyStore;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.testing.fakes.FakeCryptoBackupServer;
+import com.android.server.testing.shadows.ShadowRecoveryController;
+
+import java.security.SecureRandom;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.SecretKey;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+@Config(shadows = {ShadowRecoveryController.class, ShadowRecoveryController.class})
+public class RotateSecondaryKeyTaskTest {
+    private static final String APP_1 = "app1";
+    private static final String APP_2 = "app2";
+    private static final String APP_3 = "app3";
+
+    private static final String CURRENT_SECONDARY_KEY_ALIAS =
+            "recoverablekey.alias/d524796bd07de3c2225c63d434eff698";
+    private static final String NEXT_SECONDARY_KEY_ALIAS =
+            "recoverablekey.alias/6c6d198a7f12e662b6bc45f4849db170";
+
+    private Application mApplication;
+    private RotateSecondaryKeyTask mTask;
+    private RecoveryController mRecoveryController;
+    private FakeCryptoBackupServer mBackupServer;
+    private CryptoSettings mCryptoSettings;
+    private Map<String, SecretKey> mTertiaryKeysByPackageName;
+    private RecoverableKeyStoreSecondaryKeyManager mRecoverableSecondaryKeyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mApplication = ApplicationProvider.getApplicationContext();
+
+        mTertiaryKeysByPackageName = new HashMap<>();
+        mTertiaryKeysByPackageName.put(APP_1, generateAesKey());
+        mTertiaryKeysByPackageName.put(APP_2, generateAesKey());
+        mTertiaryKeysByPackageName.put(APP_3, generateAesKey());
+
+        mRecoveryController = RecoveryController.getInstance(mApplication);
+        mRecoverableSecondaryKeyManager =
+                new RecoverableKeyStoreSecondaryKeyManager(
+                        RecoveryController.getInstance(mApplication), new SecureRandom());
+        mBackupServer = new FakeCryptoBackupServer();
+        mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication);
+        addNextSecondaryKeyToRecoveryController();
+        mCryptoSettings.setNextSecondaryAlias(NEXT_SECONDARY_KEY_ALIAS);
+
+        mTask =
+                new RotateSecondaryKeyTask(
+                        mApplication,
+                        mRecoverableSecondaryKeyManager,
+                        mBackupServer,
+                        mCryptoSettings,
+                        mRecoveryController);
+
+        ShadowRecoveryController.reset();
+    }
+
+    @Test
+    public void run_failsIfThereIsNoActiveSecondaryKey() throws Exception {
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertFalse(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent());
+    }
+
+    @Test
+    public void run_failsIfActiveSecondaryIsNotInRecoveryController() throws Exception {
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        // Have to add it first as otherwise CryptoSettings throws an exception when trying to set
+        // it
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_doesNothingIfFlagIsDisabled() throws Exception {
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+        addWrappedTertiaries();
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_setsActiveSecondary() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+        addWrappedTertiaries();
+
+        mTask.run();
+
+        assertThat(mBackupServer.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_rewrapsExistingTertiaryKeys() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+        addWrappedTertiaries();
+
+        mTask.run();
+
+        Map<String, WrappedKeyProto.WrappedKey> rewrappedKeys =
+                mBackupServer.getAllTertiaryKeys(NEXT_SECONDARY_KEY_ALIAS);
+        SecretKey secondaryKey = (SecretKey) mRecoveryController.getKey(NEXT_SECONDARY_KEY_ALIAS);
+        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
+            WrappedKeyProto.WrappedKey rewrappedKey = rewrappedKeys.get(packageName);
+            assertThat(KeyWrapUtils.unwrap(secondaryKey, rewrappedKey))
+                    .isEqualTo(mTertiaryKeysByPackageName.get(packageName));
+        }
+    }
+
+    @Test
+    public void run_persistsRewrappedKeysToDisk() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+        addWrappedTertiaries();
+
+        mTask.run();
+
+        RecoverableKeyStoreSecondaryKey secondaryKey = getRecoverableKey(NEXT_SECONDARY_KEY_ALIAS);
+        Map<String, SecretKey> keys =
+                TertiaryKeyStore.newInstance(mApplication, secondaryKey).getAll();
+        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
+            SecretKey tertiaryKey = mTertiaryKeysByPackageName.get(packageName);
+            SecretKey newlyWrappedKey = keys.get(packageName);
+            assertThat(tertiaryKey.getEncoded()).isEqualTo(newlyWrappedKey.getEncoded());
+        }
+    }
+
+    @Test
+    public void run_stillSetsActiveSecondaryIfNoTertiaries() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mBackupServer.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_setsActiveSecondaryKeyAliasInSettings() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_removesNextSecondaryKeyAliasInSettings() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
+    }
+
+    @Test
+    public void run_deletesOldKeyFromRecoverableKeyStoreLoader() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mRecoveryController.getKey(CURRENT_SECONDARY_KEY_ALIAS)).isNull();
+    }
+
+    @Test
+    public void run_doesNotRotateIfNoNextAlias() throws Exception {
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+        mCryptoSettings.removeNextSecondaryKeyAlias();
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
+        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
+    }
+
+    @Test
+    public void run_doesNotRotateIfKeyIsNotSyncedYet() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_doesNotClearNextKeyIfSyncIsJustPending() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get())
+                .isEqualTo(NEXT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_doesNotRotateIfPermanentFailure() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get())
+                .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS);
+    }
+
+    @Test
+    public void run_removesNextKeyIfPermanentFailure() throws Exception {
+        addNextSecondaryKeyToRecoveryController();
+        setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE);
+        addCurrentSecondaryKeyToRecoveryController();
+        mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS);
+        mBackupServer.setActiveSecondaryKeyAlias(
+                CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap());
+
+        mTask.run();
+
+        assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent());
+    }
+
+    private void setNextKeyRecoveryStatus(int status) throws Exception {
+        mRecoveryController.setRecoveryStatus(NEXT_SECONDARY_KEY_ALIAS, status);
+    }
+
+    private void addCurrentSecondaryKeyToRecoveryController() throws Exception {
+        mRecoveryController.generateKey(CURRENT_SECONDARY_KEY_ALIAS);
+    }
+
+    private void addNextSecondaryKeyToRecoveryController() throws Exception {
+        mRecoveryController.generateKey(NEXT_SECONDARY_KEY_ALIAS);
+    }
+
+    private void addWrappedTertiaries() throws Exception {
+        TertiaryKeyStore tertiaryKeyStore =
+                TertiaryKeyStore.newInstance(
+                        mApplication, getRecoverableKey(CURRENT_SECONDARY_KEY_ALIAS));
+
+        for (String packageName : mTertiaryKeysByPackageName.keySet()) {
+            tertiaryKeyStore.save(packageName, mTertiaryKeysByPackageName.get(packageName));
+        }
+    }
+
+    private RecoverableKeyStoreSecondaryKey getRecoverableKey(String alias) throws Exception {
+        return mRecoverableSecondaryKeyManager.get(alias).get();
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
index 5cff53f..5dfd5ee 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java
@@ -78,7 +78,10 @@
             int orderingType,
             ChunksMetadataProto.Chunk... chunks) {
         ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing();
-        chunkListing.fingerprintMixerSalt = Arrays.copyOf(fingerprintSalt, fingerprintSalt.length);
+        chunkListing.fingerprintMixerSalt =
+                fingerprintSalt == null
+                        ? null
+                        : Arrays.copyOf(fingerprintSalt, fingerprintSalt.length);
         chunkListing.cipherType = cipherType;
         chunkListing.chunkOrderingType = orderingType;
         chunkListing.chunks = chunks;
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java
new file mode 100644
index 0000000..3329060
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing.fakes;
+
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.client.UnexpectedActiveSecondaryOnServerException;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+/** Fake {@link CryptoBackupServer}, for tests. Stores tertiary keys in memory. */
+public class FakeCryptoBackupServer implements CryptoBackupServer {
+    @GuardedBy("this")
+    @Nullable
+    private String mActiveSecondaryKeyAlias;
+
+    // Secondary key alias -> (package name -> tertiary key)
+    @GuardedBy("this")
+    private Map<String, Map<String, WrappedKeyProto.WrappedKey>> mWrappedKeyStore = new HashMap<>();
+
+    @Override
+    public String uploadIncrementalBackup(
+            String packageName,
+            String oldDocId,
+            byte[] diffScript,
+            WrappedKeyProto.WrappedKey tertiaryKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String uploadNonIncrementalBackup(
+            String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public synchronized void setActiveSecondaryKeyAlias(
+            String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+        mActiveSecondaryKeyAlias = keyAlias;
+
+        mWrappedKeyStore.putIfAbsent(keyAlias, new HashMap<>());
+        Map<String, WrappedKeyProto.WrappedKey> keyStore = mWrappedKeyStore.get(keyAlias);
+
+        for (String packageName : tertiaryKeys.keySet()) {
+            keyStore.put(packageName, tertiaryKeys.get(packageName));
+        }
+    }
+
+    public synchronized Optional<String> getActiveSecondaryKeyAlias() {
+        return Optional.ofNullable(mActiveSecondaryKeyAlias);
+    }
+
+    public synchronized Map<String, WrappedKeyProto.WrappedKey> getAllTertiaryKeys(
+            String secondaryKeyAlias) throws UnexpectedActiveSecondaryOnServerException {
+        if (!secondaryKeyAlias.equals(mActiveSecondaryKeyAlias)) {
+            throw new UnexpectedActiveSecondaryOnServerException(
+                    String.format(
+                            Locale.US,
+                            "Requested tertiary keys wrapped with %s but %s was active secondary.",
+                            secondaryKeyAlias,
+                            mActiveSecondaryKeyAlias));
+        }
+
+        if (!mWrappedKeyStore.containsKey(secondaryKeyAlias)) {
+            return Collections.emptyMap();
+        }
+        return new HashMap<>(mWrappedKeyStore.get(secondaryKeyAlias));
+    }
+}
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java
new file mode 100644
index 0000000..4cd8333b
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.testing.fakes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertThrows;
+
+import android.util.Pair;
+
+import com.android.server.backup.encryption.client.UnexpectedActiveSecondaryOnServerException;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FakeCryptoBackupServerTest {
+    private static final String PACKAGE_NAME_1 = "package1";
+    private static final String PACKAGE_NAME_2 = "package2";
+    private static final String PACKAGE_NAME_3 = "package3";
+    private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_1 = createWrappedKey("key1");
+    private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_2 = createWrappedKey("key2");
+    private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_3 = createWrappedKey("key3");
+
+    private FakeCryptoBackupServer mServer;
+
+    @Before
+    public void setUp() {
+        mServer = new FakeCryptoBackupServer();
+    }
+
+    @Test
+    public void getActiveSecondaryKeyAlias_isInitiallyAbsent() throws Exception {
+        assertFalse(mServer.getActiveSecondaryKeyAlias().isPresent());
+    }
+
+    @Test
+    public void setActiveSecondaryKeyAlias_setsTheKeyAlias() throws Exception {
+        String keyAlias = "test";
+        mServer.setActiveSecondaryKeyAlias(keyAlias, Collections.emptyMap());
+        assertThat(mServer.getActiveSecondaryKeyAlias().get()).isEqualTo(keyAlias);
+    }
+
+    @Test
+    public void getAllTertiaryKeys_returnsWrappedKeys() throws Exception {
+        Map<String, WrappedKeyProto.WrappedKey> entries =
+                createKeyMap(
+                        new Pair<>(PACKAGE_NAME_1, PACKAGE_KEY_1),
+                        new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2));
+        String secondaryKeyAlias = "doge";
+        mServer.setActiveSecondaryKeyAlias(secondaryKeyAlias, entries);
+
+        assertThat(mServer.getAllTertiaryKeys(secondaryKeyAlias)).containsExactlyEntriesIn(entries);
+    }
+
+    @Test
+    public void addTertiaryKeys_updatesExistingSet() throws Exception {
+        String keyId = "karlin";
+        WrappedKeyProto.WrappedKey replacementKey = createWrappedKey("some replacement bytes");
+
+        mServer.setActiveSecondaryKeyAlias(
+                keyId,
+                createKeyMap(
+                        new Pair<>(PACKAGE_NAME_1, PACKAGE_KEY_1),
+                        new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2)));
+
+        mServer.setActiveSecondaryKeyAlias(
+                keyId,
+                createKeyMap(
+                        new Pair<>(PACKAGE_NAME_1, replacementKey),
+                        new Pair<>(PACKAGE_NAME_3, PACKAGE_KEY_3)));
+
+        assertThat(mServer.getAllTertiaryKeys(keyId))
+                .containsExactlyEntriesIn(
+                        createKeyMap(
+                                new Pair<>(PACKAGE_NAME_1, replacementKey),
+                                new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2),
+                                new Pair<>(PACKAGE_NAME_3, PACKAGE_KEY_3)));
+    }
+
+    @Test
+    public void getAllTertiaryKeys_throwsForUnknownSecondaryKeyAlias() throws Exception {
+        assertThrows(
+                UnexpectedActiveSecondaryOnServerException.class,
+                () -> mServer.getAllTertiaryKeys("unknown"));
+    }
+
+    @Test
+    public void uploadIncrementalBackup_throwsUnsupportedOperationException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () ->
+                        mServer.uploadIncrementalBackup(
+                                PACKAGE_NAME_1,
+                                "docid",
+                                new byte[0],
+                                new WrappedKeyProto.WrappedKey()));
+    }
+
+    @Test
+    public void uploadNonIncrementalBackup_throwsUnsupportedOperationException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () ->
+                        mServer.uploadNonIncrementalBackup(
+                                PACKAGE_NAME_1, new byte[0], new WrappedKeyProto.WrappedKey()));
+    }
+
+    private static WrappedKeyProto.WrappedKey createWrappedKey(String data) {
+        WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey();
+        wrappedKey.key = data.getBytes(Charset.forName("UTF-8"));
+        return wrappedKey;
+    }
+
+    private Map<String, WrappedKeyProto.WrappedKey> createKeyMap(
+            Pair<String, WrappedKeyProto.WrappedKey>... pairs) {
+        Map<String, WrappedKeyProto.WrappedKey> map = new HashMap<>();
+        for (Pair<String, WrappedKeyProto.WrappedKey> pair : pairs) {
+            map.put(pair.first, pair.second);
+        }
+        return map;
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
index afd722b..447e579 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationInterruptionStateProvider.java
@@ -22,6 +22,7 @@
 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.BatteryController;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -34,8 +35,9 @@
     @Inject
     public CarNotificationInterruptionStateProvider(Context context,
             NotificationFilter filter,
-            StatusBarStateController stateController) {
-        super(context, filter, stateController);
+            StatusBarStateController stateController,
+            BatteryController batteryController) {
+        super(context, filter, stateController, batteryController);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
index 58f80a4..d79849c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarBatteryController.java
@@ -257,6 +257,11 @@
         return false;
     }
 
+    @Override
+    public boolean isAodPowerSave() {
+        return false;
+    }
+
     private void notifyBatteryLevelChanged() {
         for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
             mChangeCallbacks.get(i)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 046ffc3..2ce4e97 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -76,7 +76,7 @@
                 ConfigSettingsProto.RUNTIME_NATIVE_SETTINGS);
         namespaceToFieldMap.put(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
                 ConfigSettingsProto.RUNTIME_NATIVE_BOOT_SETTINGS);
-        namespaceToFieldMap.put(DeviceConfig.NAMESPACE_STORAGE,
+        namespaceToFieldMap.put(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
                 ConfigSettingsProto.STORAGE_SETTINGS);
         namespaceToFieldMap.put(DeviceConfig.NAMESPACE_SYSTEMUI,
                 ConfigSettingsProto.SYSTEMUI_SETTINGS);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c1cc261..e11c063 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -29,6 +29,7 @@
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION" />
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/qs_carrier_group.xml
index 56efb49..f2b0606 100644
--- a/packages/SystemUI/res/layout/qs_carrier_group.xml
+++ b/packages/SystemUI/res/layout/qs_carrier_group.xml
@@ -29,6 +29,9 @@
         android:id="@+id/no_carrier_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:minWidth="48dp"
+        android:minHeight="48dp"
+        android:gravity="center_vertical"
         android:textAppearance="@style/TextAppearance.QS.Status"
         android:textDirection="locale"
         android:marqueeRepeatLimit="marquee_forever"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 739689c..a6b3be2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2758,7 +2758,7 @@
 
     private void checkIsHandlerThread() {
         if (!mHandler.getLooper().isCurrentThread()) {
-            Log.wtf(TAG, "must call on mHandler's thread "
+            Log.wtfStack(TAG, "must call on mHandler's thread "
                     + mHandler.getLooper().getThread() + ", not " + Thread.currentThread());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 0fa80ac..d9b4297 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -18,6 +18,8 @@
 
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.power.PowerUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsModule;
 
 import dagger.Binds;
 import dagger.Module;
@@ -27,7 +29,7 @@
 /**
  * SystemUI objects that are injectable should go here.
  */
-@Module
+@Module(includes = {RecentsModule.class})
 public abstract class SystemUIBinder {
     /** Inject into KeyguardViewMediator. */
     @Binds
@@ -40,4 +42,10 @@
     @IntoMap
     @ClassKey(PowerUI.class)
     public abstract SystemUI bindPowerUI(PowerUI sysui);
+
+    /** Inject into StatusBar. */
+    @Binds
+    @IntoMap
+    @ClassKey(Recents.class)
+    public abstract SystemUI bindRecents(Recents sysui);
 }
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 bf81e1d..9958124 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -152,7 +152,8 @@
                     .setSubtype(Dependency.get(AssistManager.class).toLoggingSubType(type)));
         }
         // Logs assistant invocation cancelled.
-        if (!mInvocationAnimator.isRunning() && invocationWasInProgress && progress == 0f) {
+        if ((mInvocationAnimator == null || !mInvocationAnimator.isRunning())
+                && invocationWasInProgress && progress == 0f) {
             if (VERBOSE) {
                 Log.v(TAG, "Invocation cancelled: type=" + type);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 2bbe775..ca4ec6d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.wakelock.DelayedWakeLock;
@@ -66,7 +67,7 @@
                 params);
 
         DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
-                wakefulnessLifecycle, dozeLog);
+                wakefulnessLifecycle, Dependency.get(BatteryController.class), dozeLog);
         machine.setParts(new DozeMachine.Part[]{
                 new DozePauser(handler, machine, alarmManager, params.getPolicy()),
                 new DozeFalsingManagerAdapter(falsingManager),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index ba81d2f..75b1d6c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.wakelock.WakeLock;
 
@@ -122,6 +123,7 @@
     private final WakeLock mWakeLock;
     private final AmbientDisplayConfiguration mConfig;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final BatteryController mBatteryController;
     private Part[] mParts;
 
     private final ArrayList<State> mQueuedRequests = new ArrayList<>();
@@ -130,11 +132,13 @@
     private boolean mWakeLockHeldForCurrentState = false;
 
     public DozeMachine(Service service, AmbientDisplayConfiguration config,
-            WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle, DozeLog dozeLog) {
+            WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
+            BatteryController batteryController, DozeLog dozeLog) {
         mDozeService = service;
         mConfig = config;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mWakeLock = wakeLock;
+        mBatteryController = batteryController;
         mDozeLog = dozeLog;
     }
 
@@ -318,6 +322,9 @@
             Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
             return mState;
         }
+        if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) {
+            return State.DOZE;
+        }
         if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) {
             Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState);
             return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 74dec35..b212884 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -348,7 +348,6 @@
 
     private void checkTriggersAtInit() {
         if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
-                || mDozeHost.isPowerSaveActive()
                 || mDozeHost.isBlockingDoze()
                 || !mDozeHost.isProvisioned()) {
             mMachine.requestState(DozeMachine.State.FINISH);
@@ -484,8 +483,8 @@
 
         @Override
         public void onPowerSaveChanged(boolean active) {
-            if (active) {
-                mMachine.requestState(DozeMachine.State.FINISH);
+            if (mDozeHost.isPowerSaveActive()) {
+                mMachine.requestState(DozeMachine.State.DOZE);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 1cfe517..2c0ccd21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -86,7 +86,8 @@
      */
     private void updateAnimateScreenOff() {
         if (mCanAnimateTransition) {
-            final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing;
+            final boolean controlScreenOff = mDozeParameters.getAlwaysOn() && mKeyguardShowing
+                    && !mHost.isPowerSaveActive();
             mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
             mHost.setAnimateScreenOff(controlScreenOff);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 2542abd..bd3297b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -277,20 +277,7 @@
                     selectPosition(holder.getAdapterPosition(), v);
                 }
             });
-            if (mNeedsFocus) {
-                // Wait for this to get laid out then set its focus.
-                // Ensure that tile gets laid out so we get the callback.
-                holder.mTileView.requestLayout();
-                holder.mTileView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
-                    @Override
-                    public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                        holder.mTileView.removeOnLayoutChangeListener(this);
-                        holder.mTileView.requestFocus();
-                    }
-                });
-                mNeedsFocus = false;
-            }
+            focusOnHolder(holder);
             return;
         }
 
@@ -330,16 +317,38 @@
                         } else {
                             if (position < mEditIndex && canRemoveTiles()) {
                                 showAccessibilityDialog(position, v);
+                            } else if (position < mEditIndex && !canRemoveTiles()) {
+                                startAccessibleMove(position);
                             } else {
                                 startAccessibleAdd(position);
                             }
                         }
                     }
                 });
+                if (position == mAccessibilityFromIndex) {
+                    focusOnHolder(holder);
+                }
             }
         }
     }
 
+    private void focusOnHolder(Holder holder) {
+        if (mNeedsFocus) {
+            // Wait for this to get laid out then set its focus.
+            // Ensure that tile gets laid out so we get the callback.
+            holder.mTileView.requestLayout();
+            holder.mTileView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    holder.mTileView.removeOnLayoutChangeListener(this);
+                    holder.mTileView.requestFocus();
+                }
+            });
+            mNeedsFocus = false;
+        }
+    }
+
     private boolean canRemoveTiles() {
         return mCurrentSpecs.size() > mMinNumTiles;
     }
@@ -396,6 +405,7 @@
         mAccessibilityFromIndex = position;
         mAccessibilityFromLabel = mTiles.get(position).state.label;
         mAccessibilityAction = ACTION_MOVE;
+        mNeedsFocus = true;
         notifyDataSetChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 4de42cc..22470c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -95,6 +95,9 @@
 
     @Override
     public Intent getLongClickIntent() {
+        if (getState().state == Tile.STATE_UNAVAILABLE) {
+            return new Intent(Settings.ACTION_WIRELESS_SETTINGS);
+        }
         return getCellularSettingIntent();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 0fc4fe7..a1b4a93 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -21,25 +21,30 @@
 import android.graphics.Rect;
 import android.provider.Settings;
 
-import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.statusbar.CommandQueue;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+
 /**
  * A proxy to a Recents implementation.
  */
 public class Recents extends SystemUI implements CommandQueue.Callbacks {
 
-    private RecentsImplementation mImpl;
+    private final RecentsImplementation mImpl;
+
+    @Inject
+    public Recents(RecentsImplementation impl) {
+        mImpl = impl;
+    }
 
     @Override
     public void start() {
         getComponent(CommandQueue.class).addCallback(this);
         putComponent(Recents.class, this);
-        mImpl = createRecentsImplementationFromConfig();
         mImpl.onStart(mContext, this);
     }
 
@@ -139,28 +144,6 @@
                 (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
     }
 
-    /**
-     * @return The recents implementation from the config.
-     */
-    private RecentsImplementation createRecentsImplementationFromConfig() {
-        final String clsName = mContext.getString(R.string.config_recentsComponent);
-        if (clsName == null || clsName.length() == 0) {
-            throw new RuntimeException("No recents component configured", null);
-        }
-        Class<?> cls = null;
-        try {
-            cls = mContext.getClassLoader().loadClass(clsName);
-        } catch (Throwable t) {
-            throw new RuntimeException("Error loading recents component: " + clsName, t);
-        }
-        try {
-            RecentsImplementation impl = (RecentsImplementation) cls.newInstance();
-            return impl;
-        } catch (Throwable t) {
-            throw new RuntimeException("Error creating recents component: " + clsName, t);
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mImpl.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.java
new file mode 100644
index 0000000..5555285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsModule.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.recents;
+
+import android.content.Context;
+
+import com.android.systemui.R;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger injection module for {@link RecentsImplementation}
+ */
+@Module
+public class RecentsModule {
+    /**
+     * @return The {@link RecentsImplementation} from the config.
+     */
+    @Provides
+    public RecentsImplementation provideRecentsImpl(Context context) {
+        final String clsName = context.getString(R.string.config_recentsComponent);
+        if (clsName == null || clsName.length() == 0) {
+            throw new RuntimeException("No recents component configured", null);
+        }
+        Class<?> cls = null;
+        try {
+            cls = context.getClassLoader().loadClass(clsName);
+        } catch (Throwable t) {
+            throw new RuntimeException("Error loading recents component: " + clsName, t);
+        }
+        try {
+            RecentsImplementation impl = (RecentsImplementation) cls.newInstance();
+            return impl;
+        } catch (Throwable t) {
+            throw new RuntimeException("Error creating recents component: " + clsName, t);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index a70dc7c..e516af5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -27,7 +27,6 @@
 import android.view.MotionEvent
 import android.view.VelocityTracker
 import android.view.ViewConfiguration
-import com.android.systemui.Dependency
 
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.Interpolators
@@ -58,7 +57,8 @@
     private val bypassController: KeyguardBypassController,
     private val headsUpManager: HeadsUpManagerPhone,
     private val roundnessManager: NotificationRoundnessManager,
-    private val statusBarStateController: StatusBarStateController
+    private val statusBarStateController: StatusBarStateController,
+    private val falsingManager: FalsingManager
 ) : Gefingerpoken {
     companion object {
         private val RUBBERBAND_FACTOR_STATIC = 0.25f
@@ -99,7 +99,6 @@
     private val mTemp2 = IntArray(2)
     private var mDraggedFarEnough: Boolean = false
     private var mStartingChild: ExpandableView? = null
-    private val mFalsingManager: FalsingManager
     private var mPulsing: Boolean = false
     var isWakingToShadeLocked: Boolean = false
         private set
@@ -109,7 +108,7 @@
     private var velocityTracker: VelocityTracker? = null
 
     private val isFalseTouch: Boolean
-        get() = mFalsingManager.isFalseTouch
+        get() = falsingManager.isFalseTouch
     var qsExpanded: Boolean = false
     var pulseExpandAbortListener: Runnable? = null
     var bouncerShowing: Boolean = false
@@ -118,7 +117,6 @@
         mMinDragDistance = context.resources.getDimensionPixelSize(
                 R.dimen.keyguard_drag_down_min_distance)
         mTouchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
-        mFalsingManager = Dependency.get(FalsingManager::class.java)
         mPowerManager = context.getSystemService(PowerManager::class.java)
     }
 
@@ -151,7 +149,7 @@
             MotionEvent.ACTION_MOVE -> {
                 val h = y - mInitialTouchY
                 if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) {
-                    mFalsingManager.onStartExpandingFromPulse()
+                    falsingManager.onStartExpandingFromPulse()
                     isExpanding = true
                     captureStartingChild(mInitialTouchX, mInitialTouchY)
                     mInitialTouchY = y
@@ -192,7 +190,7 @@
                 velocityTracker!!.computeCurrentVelocity(1000 /* units */)
                 val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
                         statusBarStateController.state != StatusBarState.SHADE
-                if (!mFalsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
+                if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
                     finishExpansion()
                 } else {
                     cancelExpansion()
@@ -297,7 +295,7 @@
 
     private fun cancelExpansion() {
         isExpanding = false
-        mFalsingManager.onExpansionFromPulseStopped()
+        falsingManager.onExpansionFromPulseStopped()
         if (mStartingChild != null) {
             reset(mStartingChild!!)
             mStartingChild = null
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 9362d2d..eadec6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -39,6 +39,7 @@
 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.policy.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
@@ -63,6 +64,7 @@
     private final Context mContext;
     private final PowerManager mPowerManager;
     private final IDreamManager mDreamManager;
+    private final BatteryController mBatteryController;
 
     private NotificationPresenter mPresenter;
     private HeadsUpManager mHeadsUpManager;
@@ -75,13 +77,14 @@
 
     @Inject
     public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
-            StatusBarStateController stateController) {
+            StatusBarStateController stateController, BatteryController batteryController) {
         this(context,
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE),
                 IDreamManager.Stub.asInterface(
                         ServiceManager.checkService(DreamService.DREAM_SERVICE)),
                 new AmbientDisplayConfiguration(context),
                 filter,
+                batteryController,
                 stateController);
     }
 
@@ -92,10 +95,12 @@
             IDreamManager dreamManager,
             AmbientDisplayConfiguration ambientDisplayConfiguration,
             NotificationFilter notificationFilter,
+            BatteryController batteryController,
             StatusBarStateController statusBarStateController) {
         mContext = context;
         mPowerManager = powerManager;
         mDreamManager = dreamManager;
+        mBatteryController = batteryController;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mNotificationFilter = notificationFilter;
         mStatusBarStateController = statusBarStateController;
@@ -293,6 +298,13 @@
             return false;
         }
 
+        if (mBatteryController.isAodPowerSave()) {
+            if (DEBUG_HEADS_UP) {
+                Log.d(TAG, "No pulsing: disabled by battery saver: " + sbn.getKey());
+            }
+            return false;
+        }
+
         if (!canAlertCommon(entry)) {
             if (DEBUG_HEADS_UP) {
                 Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
new file mode 100644
index 0000000..2396d28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.logging;
+
+import android.annotation.IntDef;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.log.RichEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event related to notifications. {@link NotifLog} stores and prints these events for debugging
+ * and triaging purposes.
+ */
+public class NotifEvent extends RichEvent {
+    public static final int TOTAL_EVENT_TYPES = 8;
+    private StatusBarNotification mSbn;
+    private Ranking mRanking;
+
+    /**
+     * Creates a NotifEvent with an event type that matches with an index in the array
+     * getSupportedEvents() and {@link EventType}.
+     *
+     * The status bar notification and ranking objects are stored as shallow copies of the current
+     * state of the event when this event occurred.
+     */
+    public NotifEvent(int logLevel, int type, String reason, StatusBarNotification sbn,
+            Ranking ranking) {
+        super(logLevel, type, reason);
+        mSbn = sbn.clone();
+        mRanking = new Ranking();
+        mRanking.populate(ranking);
+        mMessage += getExtraInfo();
+    }
+
+    private String getExtraInfo() {
+        StringBuilder extraInfo = new StringBuilder();
+
+        if (mSbn != null) {
+            extraInfo.append(" Sbn=");
+            extraInfo.append(mSbn);
+        }
+
+        if (mRanking != null) {
+            extraInfo.append(" Ranking=");
+            extraInfo.append(mRanking);
+        }
+
+        return extraInfo.toString();
+    }
+
+    /**
+     * Event labels for NotifEvents
+     * Index corresponds to the {@link EventType}
+     */
+    @Override
+    public String[] getEventLabels() {
+        final String[] events = new String[]{
+                "NotifAdded",
+                "NotifRemoved",
+                "NotifUpdated",
+                "HeadsUpStarted",
+                "HeadsUpEnded",
+                "Filter",
+                "Sort",
+                "NotifVisibilityChanged",
+        };
+
+        if (events.length != TOTAL_EVENT_TYPES) {
+            throw new IllegalStateException("NotifEvents events.length should match "
+                    + TOTAL_EVENT_TYPES
+                    + " events.length=" + events.length
+                    + " TOTAL_EVENT_LENGTH=" + TOTAL_EVENT_TYPES);
+        }
+        return events;
+    }
+
+    /**
+     * @return a copy of the status bar notification that changed with this event
+     */
+    public StatusBarNotification getSbn() {
+        return mSbn;
+    }
+
+    /**
+     * Builds a NotifEvent.
+     */
+    public static class NotifEventBuilder extends RichEvent.Builder<NotifEventBuilder> {
+        private StatusBarNotification mSbn;
+        private Ranking mRanking;
+
+        @Override
+        public NotifEventBuilder getBuilder() {
+            return this;
+        }
+
+        /**
+         * Stores the status bar notification object. A shallow copy is stored in the NotifEvent's
+         * constructor.
+         */
+        public NotifEventBuilder setSbn(StatusBarNotification sbn) {
+            mSbn = sbn;
+            return this;
+        }
+
+        /**
+         * Stores the ranking object. A shallow copy is stored in the NotifEvent's
+         * constructor.
+         */
+        public NotifEventBuilder setRanking(Ranking ranking) {
+            mRanking = ranking;
+            return this;
+        }
+
+        @Override
+        public RichEvent build() {
+            return new NotifEvent(mLogLevel, mType, mReason, mSbn, mRanking);
+        }
+    }
+
+    @IntDef({NOTIF_ADDED, NOTIF_REMOVED, NOTIF_UPDATED, HEADS_UP_STARTED, HEADS_UP_ENDED, FILTER,
+            SORT, NOTIF_VISIBILITY_CHANGED})
+    /**
+     * Types of NotifEvents
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventType {}
+    public static final int NOTIF_ADDED = 0;
+    public static final int NOTIF_REMOVED = 1;
+    public static final int NOTIF_UPDATED = 2;
+    public static final int HEADS_UP_STARTED = 3;
+    public static final int HEADS_UP_ENDED = 4;
+    public static final int FILTER = 5;
+    public static final int SORT = 6;
+    public static final int NOTIF_VISIBILITY_CHANGED = 7;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
new file mode 100644
index 0000000..d42cd82
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.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.systemui.statusbar.notification.logging;
+
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.DumpController;
+import com.android.systemui.log.SysuiLog;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Logs systemui notification events for debugging and triaging purposes. Logs are dumped in
+ * bugreports or on demand:
+ *      adb shell dumpsys activity service com.android.systemui/.SystemUIService \
+ *      dependency DumpController NotifLog
+ */
+@Singleton
+public class NotifLog extends SysuiLog {
+    private static final String TAG = "NotifLog";
+    private static final int MAX_DOZE_DEBUG_LOGS = 400;
+    private static final int MAX_DOZE_LOGS = 50;
+
+    @Inject
+    public NotifLog(DumpController dumpController) {
+        super(dumpController, TAG, MAX_DOZE_DEBUG_LOGS, MAX_DOZE_LOGS);
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a notification, ranking and message
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
+            Ranking ranking, String msg) {
+        return log(new NotifEvent.NotifEventBuilder()
+                .setType(eventType)
+                .setSbn(sbn)
+                .setRanking(ranking)
+                .setReason(msg)
+                .build());
+    }
+
+    /**
+     * Logs a {@link NotifEvent}
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType) {
+        return log(eventType, null, null, null);
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a message
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, String msg) {
+        return log(eventType, null, null, msg);
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a notification
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn) {
+        return log(eventType, sbn, null, "");
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a ranking
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, Ranking ranking) {
+        return log(eventType, null, ranking, "");
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a notification and ranking
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn,
+            Ranking ranking) {
+        return log(eventType, sbn, ranking, "");
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a notification entry
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry) {
+        return log(eventType, entry.sbn(), entry.ranking(), "");
+    }
+
+    /**
+     * Logs a {@link NotifEvent} with a notification entry
+     * @return true if successfully logged, else false
+     */
+    public boolean log(@NotifEvent.EventType int eventType, NotificationEntry entry,
+            String msg) {
+        return log(eventType, entry.sbn(), entry.ranking(), msg);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8d73251..a817f54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -180,6 +180,10 @@
         initDimens();
     }
 
+    public FalsingManager getFalsingManager() {
+        return mFalsingManager;
+    }
+
     private void updateColors() {
         mNormalColor = mContext.getColor(R.color.notification_material_background_color);
         mTintedRippleColor = mContext.getColor(
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 0f6ce21..9f4b026 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
@@ -74,7 +74,6 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
@@ -221,7 +220,6 @@
     private ViewStub mGutsStub;
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
-    private FalsingManager mFalsingManager;
     private boolean mExpandAnimationRunning;
     private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
@@ -1636,7 +1634,6 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mFalsingManager = Dependency.get(FalsingManager.class);  // TODO: inject into a controller.
         mNotificationInflater = new NotificationContentInflater(this);
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
@@ -2208,7 +2205,7 @@
      * @param allowChildExpansion whether a call to this method allows expanding children
      */
     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
-        mFalsingManager.setNotificationExpanded();
+        getFalsingManager().setNotificationExpanded();
         if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
                 && !mChildrenContainer.showingAsLowPriority()) {
             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
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 8150161..5fc2d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -396,6 +396,8 @@
     boolean mAllowNotificationLongPress;
     @Inject
     protected NotifPipelineInitializer mNotifPipelineInitializer;
+    @Inject
+    protected FalsingManager mFalsingManager;
 
     @VisibleForTesting
     BroadcastDispatcher mBroadcastDispatcher;
@@ -587,7 +589,6 @@
         }
     };
     private boolean mNoAnimationOnNextBarModeChange;
-    protected FalsingManager mFalsingManager;
     private final SysuiStatusBarStateController mStatusBarStateController =
             (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
 
@@ -719,7 +720,6 @@
         mRecents = getComponent(Recents.class);
 
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mFalsingManager = Dependency.get(FalsingManager.class);
 
         // Connect in to the status bar manager service
         mCommandQueue = getComponent(CommandQueue.class);
@@ -2448,7 +2448,7 @@
             mKeyguardUpdateMonitor.dump(fd, pw, args);
         }
 
-        Dependency.get(FalsingManager.class).dump(pw);
+        mFalsingManager.dump(pw);
         FalsingLog.dump(pw);
 
         pw.println("SharedPreferences:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 111cdd2..738d076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -45,9 +45,7 @@
     /**
      * Returns {@code true} if AOD was disabled by power saving policies.
      */
-    default boolean isAodPowerSave() {
-        return isPowerSave();
-    }
+    boolean isAodPowerSave();
 
     /**
      * A listener that will be notified whenever a change in battery level or power save mode has
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index edea92f..2c70fb4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -621,7 +621,9 @@
                 .PRIORITY_CATEGORY_MEDIA) == 0;
         boolean disallowSystem = (policy.priorityCategories & NotificationManager.Policy
                 .PRIORITY_CATEGORY_SYSTEM) == 0;
-        boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(policy);
+        // ringer controls notifications, ringer and system sounds, so only disallow ringer changes
+        // if all relevant (notifications + ringer + system) sounds are not allowed to bypass DND
+        boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(policy);
         if (mState.disallowAlarms == disallowAlarms
                 && mState.disallowMedia == disallowMedia
                 && mState.disallowRinger == disallowRinger
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 448c80e..2798c6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -74,6 +74,7 @@
 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.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -174,7 +175,8 @@
         TestableNotificationInterruptionStateProvider interruptionStateProvider =
                 new TestableNotificationInterruptionStateProvider(mContext,
                         mock(NotificationFilter.class),
-                        mock(StatusBarStateController.class));
+                        mock(StatusBarStateController.class),
+                        mock(BatteryController.class));
         interruptionStateProvider.setUpWithPresenter(
                 mock(NotificationPresenter.class),
                 mock(HeadsUpManager.class),
@@ -660,8 +662,9 @@
             NotificationInterruptionStateProvider {
 
         TestableNotificationInterruptionStateProvider(Context context,
-                NotificationFilter filter, StatusBarStateController controller) {
-            super(context, filter, controller);
+                NotificationFilter filter, StatusBarStateController controller,
+                BatteryController batteryController) {
+            super(context, filter, controller, batteryController);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 9d42b75..bbd2ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -46,6 +46,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.Before;
@@ -79,9 +80,7 @@
         mPartMock = mock(DozeMachine.Part.class);
 
         mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
-                mWakefulnessLifecycle,
-                mDozeLog);
-
+                mWakefulnessLifecycle, mock(BatteryController.class), mDozeLog);
         mMachine.setParts(new DozeMachine.Part[]{mPartMock});
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
index 350ab5a..28a7e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInterruptionStateProviderTest.java
@@ -52,6 +52,7 @@
 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.BatteryController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -84,6 +85,8 @@
     HeadsUpManager mHeadsUpManager;
     @Mock
     NotificationInterruptionStateProvider.HeadsUpSuppressor mHeadsUpSuppressor;
+    @Mock
+    BatteryController mBatteryController;
 
     private NotificationInterruptionStateProvider mNotifInterruptionStateProvider;
 
@@ -97,7 +100,8 @@
                         mDreamManager,
                         mAmbientDisplayConfiguration,
                         mNotificationFilter,
-                        mStatusBarStateController);
+                        mStatusBarStateController,
+                        mBatteryController);
 
         mNotifInterruptionStateProvider.setUpWithPresenter(
                 mPresenter,
@@ -590,17 +594,17 @@
     /**
      * Testable class overriding constructor.
      */
-    public class TestableNotificationInterruptionStateProvider extends
+    public static class TestableNotificationInterruptionStateProvider extends
             NotificationInterruptionStateProvider {
 
         TestableNotificationInterruptionStateProvider(Context context,
                 PowerManager powerManager, IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 NotificationFilter notificationFilter,
-                StatusBarStateController statusBarStateController) {
+                StatusBarStateController statusBarStateController,
+                BatteryController batteryController) {
             super(context, powerManager, dreamManager, ambientDisplayConfiguration,
-                    notificationFilter,
-                    statusBarStateController);
+                    notificationFilter, batteryController, statusBarStateController);
         }
     }
 }
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 b5ef716..f1da4e8 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
@@ -41,6 +41,7 @@
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
@@ -130,9 +131,13 @@
                         mock(HeadsUpManagerPhone.class),
                         new StatusBarStateControllerImpl(),
                         mKeyguardBypassController);
-        PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator,
+        PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
+                mContext,
+                coordinator,
                 mKeyguardBypassController, mHeadsUpManager,
-                mock(NotificationRoundnessManager.class), mStatusBarStateController);
+                mock(NotificationRoundnessManager.class),
+                mStatusBarStateController,
+                new FalsingManagerFake());
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
                 mKeyguardBypassController);
         mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
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 9932a14..32b0f76 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
@@ -113,6 +113,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -159,6 +160,7 @@
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
     @Mock private RemoteInputController mRemoteInputController;
     @Mock private StatusBarStateControllerImpl mStatusBarStateController;
+    @Mock private BatteryController mBatteryController;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBarNotificationPresenter mNotificationPresenter;
     @Mock
@@ -214,7 +216,7 @@
         mNotificationInterruptionStateProvider =
                 new TestableNotificationInterruptionStateProvider(mContext, mPowerManager,
                         mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
-                        mStatusBarStateController);
+                        mStatusBarStateController, mBatteryController);
         mDependency.injectTestDependency(NotificationInterruptionStateProvider.class,
                 mNotificationInterruptionStateProvider);
         mDependency.injectMockDependency(NavigationBarController.class);
@@ -906,9 +908,10 @@
                 IDreamManager dreamManager,
                 AmbientDisplayConfiguration ambientDisplayConfiguration,
                 NotificationFilter filter,
-                StatusBarStateController controller) {
+                StatusBarStateController controller,
+                BatteryController batteryController) {
             super(context, powerManager, dreamManager, ambientDisplayConfiguration, filter,
-                    controller);
+                    batteryController, controller);
             mUseHeadsUp = true;
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index a843cca..df76f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -48,4 +48,9 @@
     public boolean isPowerSave() {
         return false;
     }
+
+    @Override
+    public boolean isAodPowerSave() {
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 34111a9..67686e0 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -814,7 +814,7 @@
                 }
             });
         // For now, simply clone property when it changes
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE,
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
                 mContext.getMainExecutor(), (properties) -> {
                     refreshIsolatedStorageSettings();
                 });
@@ -854,7 +854,8 @@
         // Always copy value from newer DeviceConfig location
         Settings.Global.putString(mResolver,
                 Settings.Global.ISOLATED_STORAGE_REMOTE,
-                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE, ISOLATED_STORAGE_ENABLED));
+                DeviceConfig.getProperty(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                        ISOLATED_STORAGE_ENABLED));
 
         final int local = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.ISOLATED_STORAGE_LOCAL, 0);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5947c35..ecbbef1 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -208,6 +208,10 @@
 
     private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
 
+    private EmergencyNumber[] mOutgoingSmsEmergencyNumber;
+
+    private EmergencyNumber[] mOutgoingCallEmergencyNumber;
+
     private CallQuality[] mCallQuality;
 
     private CallAttributes[] mCallAttributes;
@@ -266,6 +270,10 @@
                 PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
                 PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
 
+    static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
+            PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER
+                    | PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER;
+
     private static final int MSG_USER_SWITCHED = 1;
     private static final int MSG_UPDATE_DEFAULT_SUB = 2;
 
@@ -406,6 +414,8 @@
         mImsReasonInfo = new ArrayList<>();
         mPhysicalChannelConfigs = new ArrayList<>();
         mEmergencyNumberList = new HashMap<>();
+        mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
+        mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
             mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -1910,6 +1920,56 @@
     }
 
     @Override
+    public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+            EmergencyNumber emergencyNumber) {
+        if (!checkNotifyPermission("notifyOutgoingEmergencyCall()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mOutgoingCallEmergencyNumber[phoneId] = emergencyNumber;
+                for (Record r : mRecords) {
+                    if (r.matchPhoneStateListenerEvent(
+                            PhoneStateListener.LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER)
+                                    && idMatch(r.subId, subId, phoneId)) {
+                        try {
+                            r.callback.onOutgoingEmergencyCall(emergencyNumber);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
+    public void notifyOutgoingEmergencySms(int phoneId, int subId,
+            EmergencyNumber emergencyNumber) {
+        if (!checkNotifyPermission("notifyOutgoingEmergencySms()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                mOutgoingSmsEmergencyNumber[phoneId] = emergencyNumber;
+                for (Record r : mRecords) {
+                    if (r.matchPhoneStateListenerEvent(
+                            PhoneStateListener.LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER)
+                                    && idMatch(r.subId, subId, phoneId)) {
+                        try {
+                            r.callback.onOutgoingEmergencySms(emergencyNumber);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void notifyCallQualityChanged(CallQuality callQuality, int phoneId, int subId,
             int callNetworkType) {
         if (!checkNotifyPermission("notifyCallQualityChanged()")) {
@@ -1981,6 +2041,8 @@
                 pw.println("mCallAttributes=" + mCallAttributes[i]);
                 pw.println("mCallNetworkType=" + mCallNetworkType[i]);
                 pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
+                pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
+                pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
                 pw.decreaseIndent();
             }
             pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
@@ -2250,6 +2312,11 @@
                     android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
         }
 
+        if ((events & READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION, null);
+        }
+
         if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
             mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7b69bea..1cb91d5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -233,6 +233,7 @@
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.BinderProxy;
+import android.os.BugreportParams;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
@@ -8255,22 +8256,22 @@
             @Nullable String shareDescription, int bugreportType) {
         String type = null;
         switch (bugreportType) {
-            case ActivityManager.BUGREPORT_OPTION_FULL:
+            case BugreportParams.BUGREPORT_MODE_FULL:
                 type = "bugreportfull";
                 break;
-            case ActivityManager.BUGREPORT_OPTION_INTERACTIVE:
+            case BugreportParams.BUGREPORT_MODE_INTERACTIVE:
                 type = "bugreportplus";
                 break;
-            case ActivityManager.BUGREPORT_OPTION_REMOTE:
+            case BugreportParams.BUGREPORT_MODE_REMOTE:
                 type = "bugreportremote";
                 break;
-            case ActivityManager.BUGREPORT_OPTION_WEAR:
+            case BugreportParams.BUGREPORT_MODE_WEAR:
                 type = "bugreportwear";
                 break;
-            case ActivityManager.BUGREPORT_OPTION_TELEPHONY:
+            case BugreportParams.BUGREPORT_MODE_TELEPHONY:
                 type = "bugreporttelephony";
                 break;
-            case ActivityManager.BUGREPORT_OPTION_WIFI:
+            case BugreportParams.BUGREPORT_MODE_WIFI:
                 type = "bugreportwifi";
                 break;
             default:
@@ -8305,7 +8306,7 @@
         final boolean useApi = FeatureFlagUtils.isEnabled(mContext,
                 FeatureFlagUtils.USE_BUGREPORT_API);
 
-        if (useApi && bugreportType == ActivityManager.BUGREPORT_OPTION_INTERACTIVE) {
+        if (useApi && bugreportType == BugreportParams.BUGREPORT_MODE_INTERACTIVE) {
             // Create intent to trigger Bugreport API via Shell
             Intent triggerShellBugreport = new Intent();
             triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED);
@@ -8341,7 +8342,7 @@
     @Override
     public void requestTelephonyBugReport(String shareTitle, String shareDescription) {
         requestBugReportWithDescription(shareTitle, shareDescription,
-                ActivityManager.BUGREPORT_OPTION_TELEPHONY);
+                BugreportParams.BUGREPORT_MODE_TELEPHONY);
     }
 
     /**
@@ -8353,7 +8354,7 @@
     @Override
     public void requestWifiBugReport(String shareTitle, String shareDescription) {
         requestBugReportWithDescription(shareTitle, shareDescription,
-                ActivityManager.BUGREPORT_OPTION_WIFI);
+                BugreportParams.BUGREPORT_MODE_WIFI);
     }
 
     /**
@@ -8361,7 +8362,7 @@
      */
     @Override
     public void requestInteractiveBugReport() {
-        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+        requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE);
     }
 
     /**
@@ -8372,7 +8373,7 @@
     public void requestInteractiveBugReportWithDescription(String shareTitle,
             String shareDescription) {
         requestBugReportWithDescription(shareTitle, shareDescription,
-                ActivityManager.BUGREPORT_OPTION_INTERACTIVE);
+                BugreportParams.BUGREPORT_MODE_INTERACTIVE);
     }
 
     /**
@@ -8380,7 +8381,7 @@
      */
     @Override
     public void requestFullBugReport() {
-        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_FULL);
+        requestBugReportWithDescription(null, null,  BugreportParams.BUGREPORT_MODE_FULL);
     }
 
     /**
@@ -8388,7 +8389,7 @@
      */
     @Override
     public void requestRemoteBugReport() {
-        requestBugReportWithDescription(null, null, ActivityManager.BUGREPORT_OPTION_REMOTE);
+        requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_REMOTE);
     }
 
     public void registerProcessObserver(IProcessObserver observer) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 64f4a35..4a6e63f 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -86,6 +86,7 @@
         DeviceConfig.NAMESPACE_NETD_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
         DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
+        DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
     };
 
     private final String[] mGlobalSettings;
@@ -276,4 +277,4 @@
         String settingValue = Settings.Global.getString(mContentResolver, settingName);
         setProperty(propName, settingValue);
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 77b3fee..ccbe08f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3854,7 +3854,7 @@
         final boolean muteSystem = (zenPolicy.priorityCategories
                 & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0;
         final boolean muteNotificationAndRing = ZenModeConfig
-                .areAllPriorityOnlyNotificationZenSoundsMuted(
+                .areAllPriorityOnlyRingerSoundsMuted(
                         mNm.getConsolidatedNotificationPolicy());
         return muteAlarms && isAlarm(streamType)
                 || muteMedia && isMedia(streamType)
@@ -3867,16 +3867,26 @@
     }
 
     /**
-     * DND total silence: media and alarms streams are tied to the muted ringer
+     * Notifications, ringer and system sounds are controlled by the ringer:
      * {@link ZenModeHelper.RingerModeDelegate#getRingerModeAffectedStreams(int)}
-     * DND alarms only: notification, ringer + system muted (by default tied to muted ringer mode)
-     * DND priority only: alarms, media, system streams can be muted separate from ringer based on
+     * DND total silence: media and alarms streams can be muted by DND
+     * DND alarms only: no streams additionally controlled by DND
+     * DND priority only: alarms, media, system streams can be muted by DND based on
      * zenPolicy (this method determines which streams)
      * @return true if changed, else false
      */
     private boolean updateZenModeAffectedStreams() {
+        if (!mSystemReady) {
+            return false;
+        }
+
         int zenModeAffectedStreams = 0;
-        if (mSystemReady && mNm.getZenMode() == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+        final int zenMode = mNm.getZenMode();
+
+        if (zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
+            zenModeAffectedStreams |= 1 << AudioManager.STREAM_ALARM;
+            zenModeAffectedStreams |= 1 << AudioManager.STREAM_MUSIC;
+        } else if (zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
             NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy();
             if ((zenPolicy.priorityCategories
                     & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0) {
@@ -3888,6 +3898,8 @@
                 zenModeAffectedStreams |= 1 << AudioManager.STREAM_MUSIC;
             }
 
+            // even if zen isn't muting the system stream, the ringer mode can still mute
+            // the system stream
             if ((zenPolicy.priorityCategories
                     & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0) {
                 zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM;
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index b1c7c76..9eb0d50 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -930,7 +930,8 @@
                     final Face face = new Face("", 0 /* identifier */, deviceId);
                     FaceService.super.handleRemoved(face, 0 /* remaining */);
                 }
-
+                Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                        Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
             });
         }
 
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index cfbf8bc..96ba8ef 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -394,10 +394,6 @@
     static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
     static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
 
-    // Property name for the local device configurations.
-    // TODO(OEM): OEM should provide this property, and the value is the comma separated integer
-    //     values which denotes the device type in HDMI Spec 1.4.
-    static final String PROPERTY_DEVICE_TYPE = "ro.hdmi.device_type";
 
     // TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
     //            True by default.
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 3856de4..c3354e1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -56,8 +56,8 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputManager.TvInputCallback;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -68,6 +68,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
+import android.sysprop.HdmiProperties;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -95,6 +96,8 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Provides a service for sending and processing HDMI control messages,
@@ -452,7 +455,14 @@
 
     public HdmiControlService(Context context) {
         super(context);
-        mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
+        List<Integer> deviceTypes = HdmiProperties.device_type();
+        if (deviceTypes.contains(null)) {
+            Slog.w(TAG, "Error parsing ro.hdmi.device.type: " + SystemProperties.get(
+                    "ro.hdmi.device_type"));
+            deviceTypes = deviceTypes.stream().filter(Objects::nonNull).collect(
+                    Collectors.toList());
+        }
+        mLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 75b9705..8fbad4c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -220,6 +220,8 @@
     private static native void nativeSetFocusedApplication(long ptr,
             int displayId, InputApplicationHandle application);
     private static native void nativeSetFocusedDisplay(long ptr, int displayId);
+    private static native boolean nativeTransferTouchFocus(long ptr,
+            InputChannel fromChannel, InputChannel toChannel);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1533,6 +1535,29 @@
         nativeSetSystemUiVisibility(mPtr, visibility);
     }
 
+    /**
+     * Atomically transfers touch focus from one window to another as identified by
+     * their input channels.  It is possible for multiple windows to have
+     * touch focus if they support split touch dispatch
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+     * method only transfers touch focus of the specified window without affecting
+     * other windows that may also have touch focus at the same time.
+     * @param fromChannel The channel of a window that currently has touch focus.
+     * @param toChannel The channel of the window that should receive touch focus in
+     * place of the first.
+     * @return True if the transfer was successful.  False if the window with the
+     * specified channel did not actually have touch focus at the time of the request.
+     */
+    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+        if (fromChannel == null) {
+            throw new IllegalArgumentException("fromChannel must not be null.");
+        }
+        if (toChannel == null) {
+            throw new IllegalArgumentException("toChannel must not be null.");
+        }
+        return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+    }
+
     @Override // Binder call
     public void tryPointerSpeed(int speed) {
         if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b7eca29..378d9eb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -44,6 +44,7 @@
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.admin.PasswordMetrics;
 import android.app.backup.BackupManager;
 import android.app.trust.IStrongAuthTracker;
@@ -76,6 +77,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
@@ -429,6 +431,10 @@
             return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         }
 
+        public UserManagerInternal getUserManagerInternal() {
+            return LocalServices.getService(UserManagerInternal.class);
+        }
+
         /**
          * Return the {@link DevicePolicyManager} object.
          *
@@ -440,6 +446,10 @@
             return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
 
+        public DeviceStateCache getDeviceStateCache() {
+            return DeviceStateCache.getInstance();
+        }
+
         public KeyStore getKeyStore() {
             return KeyStore.getInstance();
         }
@@ -1023,11 +1033,16 @@
     }
 
     private void notifySeparateProfileChallengeChanged(int userId) {
-        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
-                DevicePolicyManagerInternal.class);
-        if (dpmi != null) {
-            dpmi.reportSeparateProfileChallengeChanged(userId);
-        }
+        // LSS cannot call into DPM directly, otherwise it will cause deadlock.
+        // In this case, calling DPM on a handler thread is OK since DPM doesn't
+        // expect reportSeparateProfileChallengeChanged() to happen synchronously.
+        mHandler.post(() -> {
+            final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                    DevicePolicyManagerInternal.class);
+            if (dpmi != null) {
+                dpmi.reportSeparateProfileChallengeChanged(userId);
+            }
+        });
     }
 
     @Override
@@ -2038,7 +2053,6 @@
      * reporting the password changed.
      */
     private void notifyPasswordChanged(@UserIdInt int userId) {
-        // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
         mHandler.post(() -> {
             mInjector.getDevicePolicyManager().reportPasswordChanged(userId);
             LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
@@ -3026,45 +3040,43 @@
         pw.decreaseIndent();
     }
 
+    /**
+     * Cryptographically disable escrow token support for the current user, if the user is not
+     * managed (either user has a profile owner, or if device is managed). Do not disable
+     * if we are running an automotive build.
+     */
     private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            // Managed profile should have escrow enabled
-            if (mUserManager.getUserInfo(userId).isManagedProfile()) {
-                Slog.i(TAG, "Managed profile can have escrow token");
-                return;
-            }
-            DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
-            // Devices with Device Owner should have escrow enabled on all users.
-            if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
-                Slog.i(TAG, "Corp-owned device can have escrow token");
-                return;
-            }
-            // We could also have a profile owner on the given (non-managed) user for unicorn cases
-            if (dpm.getProfileOwnerAsUser(userId) != null) {
-                Slog.i(TAG, "User with profile owner can have escrow token");
-                return;
-            }
-            // If the device is yet to be provisioned (still in SUW), there is still
-            // a chance that Device Owner will be set on the device later, so postpone
-            // disabling escrow token for now.
-            if (!dpm.isDeviceProvisioned()) {
-                Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
-                return;
-            }
+        final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
 
-            // Escrow tokens are enabled on automotive builds.
-            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                return;
-            }
+        // Managed profile should have escrow enabled
+        if (userManagerInternal.isUserManaged(userId)) {
+            Slog.i(TAG, "Managed profile can have escrow token");
+            return;
+        }
 
-            // Disable escrow token permanently on all other device/user types.
-            Slog.i(TAG, "Disabling escrow token on user " + userId);
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                mSpManager.destroyEscrowData(userId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+        // Devices with Device Owner should have escrow enabled on all users.
+        if (userManagerInternal.isDeviceManaged()) {
+            Slog.i(TAG, "Corp-owned device can have escrow token");
+            return;
+        }
+
+        // If the device is yet to be provisioned (still in SUW), there is still
+        // a chance that Device Owner will be set on the device later, so postpone
+        // disabling escrow token for now.
+        if (!mInjector.getDeviceStateCache().isDeviceProvisioned()) {
+            Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
+            return;
+        }
+
+        // Escrow tokens are enabled on automotive builds.
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            return;
+        }
+
+        // Disable escrow token permanently on all other device/user types.
+        Slog.i(TAG, "Disabling escrow token on user " + userId);
+        if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+            mSpManager.destroyEscrowData(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index ea0fb47..9246311 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -129,6 +129,9 @@
             keyStore.load(null);
 
             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+            if (decryptionKey == null) {
+                throw new IllegalStateException("SP key is missing: " + keyAlias);
+            }
             byte[] intermediate = decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, blob);
             return decrypt(decryptionKey, intermediate);
         } catch (Exception e) {
@@ -143,6 +146,9 @@
             keyStore.load(null);
 
             SecretKey decryptionKey = (SecretKey) keyStore.getKey(keyAlias, null);
+            if (decryptionKey == null) {
+                throw new IllegalStateException("SP key is missing: " + keyAlias);
+            }
             byte[] intermediate = decrypt(decryptionKey, blob);
             return decrypt(applicationId, APPLICATION_ID_PERSONALIZATION, intermediate);
         } catch (CertificateException | IOException | BadPaddingException
@@ -193,6 +199,7 @@
             keyStore = KeyStore.getInstance("AndroidKeyStore");
             keyStore.load(null);
             keyStore.deleteEntry(keyAlias);
+            Slog.i(TAG, "SP key deleted: " + keyAlias);
         } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
                 | IOException e) {
             Slog.e(TAG, "Failed to destroy blob", e);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f63aa52..6510923 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1179,7 +1179,7 @@
 
             if (mZenMode == Global.ZEN_MODE_OFF
                     || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                    && !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(mConfig))) {
+                    && !ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(mConfig))) {
                 // in priority only with ringer not muted, save ringer mode changes
                 // in dnd off, save ringer mode changes
                 setPreviousRingerModeSetting(ringerModeNew);
@@ -1200,7 +1200,7 @@
                             && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
                             || mZenMode == Global.ZEN_MODE_ALARMS
                             || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                            && ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(
+                            && ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(
                             mConfig)))) {
                         newZen = Global.ZEN_MODE_OFF;
                     } else if (mZenMode != Global.ZEN_MODE_OFF) {
@@ -1264,29 +1264,21 @@
 
         @Override
         public int getRingerModeAffectedStreams(int streams) {
-            // ringtone and notification streams are always affected by ringer mode
-            // system stream is affected by ringer mode when not in priority-only
+            // ringtone, notification and system streams are always affected by ringer mode
+            // zen muting is handled in AudioService.java's mZenModeAffectedStreams
             streams |= (1 << AudioSystem.STREAM_RING) |
                     (1 << AudioSystem.STREAM_NOTIFICATION) |
                     (1 << AudioSystem.STREAM_SYSTEM);
 
             if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
-                // alarm and music streams affected by ringer mode when in total silence
+                // alarm and music streams affected by ringer mode (cannot be adjusted) when in
+                // total silence
                 streams |= (1 << AudioSystem.STREAM_ALARM) |
                         (1 << AudioSystem.STREAM_MUSIC);
             } else {
                 streams &= ~((1 << AudioSystem.STREAM_ALARM) |
                         (1 << AudioSystem.STREAM_MUSIC));
             }
-
-            if (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
-                    && ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(mConfig)) {
-                // system stream is not affected by ringer mode in priority only when the ringer
-                // is zen muted (all other notification categories are muted)
-                streams &= ~(1 << AudioSystem.STREAM_SYSTEM);
-            } else {
-                streams |= (1 << AudioSystem.STREAM_SYSTEM);
-            }
             return streams;
         }
     }
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 4f47afe..c404dad 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -78,6 +78,13 @@
         sMacPermissions.add(new File(
             Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"));
 
+        // SystemExt mac permissions (optional).
+        final File systemExtMacPermission = new File(
+                Environment.getSystemExtDirectory(), "/etc/selinux/system_ext_mac_permissions.xml");
+        if (systemExtMacPermission.exists()) {
+            sMacPermissions.add(systemExtMacPermission);
+        }
+
         // Product mac permissions (optional).
         final File productMacPermission = new File(
                 Environment.getProductDirectory(), "/etc/selinux/product_mac_permissions.xml");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5f86708..dace598 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3979,6 +3979,13 @@
         }
 
         @Override
+        public boolean isDeviceManaged() {
+            synchronized (mUsersLock) {
+                return mIsDeviceManaged;
+            }
+        }
+
+        @Override
         public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
             synchronized (mUsersLock) {
                 mIsUserManaged.put(userId, isManaged);
@@ -3986,6 +3993,13 @@
         }
 
         @Override
+        public boolean isUserManaged(@UserIdInt int userId) {
+            synchronized (mUsersLock) {
+                return mIsUserManaged.get(userId);
+            }
+        }
+
+        @Override
         public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -4205,6 +4219,7 @@
             return restrictions != null && restrictions.getBoolean(restrictionKey);
         }
 
+        @Override
         public @Nullable UserInfo getUserInfo(@UserIdInt int userId) {
             UserData userData;
             synchronized (mUsersLock) {
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 7953c50..fc8d520 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1302,7 +1302,7 @@
         }
 
         if (bp.isSoftRestricted() && !SoftRestrictedPermissionPolicy.forPermission(mContext,
-                pkg, UserHandle.of(userId), permName).canBeGranted()) {
+                pkg.toAppInfo(), UserHandle.of(userId), permName).mayGrantPermission()) {
             Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
                     + packageName);
             return;
@@ -3361,6 +3361,9 @@
         final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
                 | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
 
+        final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
+
         final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
                 >= Build.VERSION_CODES.M;
 
@@ -3384,12 +3387,11 @@
                                 callingUid, userId, callback);
                     }
                 } else {
-                    // In permission review mode we clear the review flag when we
-                    // are asked to install the app with all permissions granted.
-                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                        updatePermissionFlagsInternal(permission, pkg.getPackageName(),
-                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
-                                userId, false, callback);
+                    // In permission review mode we clear the review flag and the revoked compat
+                    // flag when we are asked to install the app with all permissions granted.
+                    if ((flags & compatFlags) != 0) {
+                        updatePermissionFlagsInternal(permission, pkg.getPackageName(), compatFlags,
+                                0, callingUid, userId, false, callback);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index a98de89..2f66713 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -17,13 +17,12 @@
 package com.android.server.policy;
 
 import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 
 import android.annotation.NonNull;
@@ -157,14 +156,12 @@
                     appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
                 } else if (perm.isSoftRestricted()) {
                     appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
-
                     SoftRestrictedPermissionPolicy policy =
-                            SoftRestrictedPermissionPolicy.forPermission(null,
-                                    (AndroidPackage) null, null,
+                            SoftRestrictedPermissionPolicy.forPermission(null, null, null,
                                     perm.name);
-                    if (policy.resolveAppOp() != OP_NONE) {
-                        appOpsService.startWatchingMode(policy.resolveAppOp(), null,
-                                appOpsListener);
+                    int extraAppOp = policy.getExtraAppOpCode();
+                    if (extraAppOp != OP_NONE) {
+                        appOpsService.startWatchingMode(extraAppOp, null, appOpsListener);
                     }
                 }
             }
@@ -397,24 +394,6 @@
         private final @NonNull SparseIntArray mAllUids = new SparseIntArray();
 
         /**
-         * All ops that need to be set to default
-         *
-         * Currently, only used by the restricted permissions logic.
-         *
-         * @see #syncPackages
-         */
-        private final @NonNull ArrayList<OpToChange> mOpsToDefault = new ArrayList<>();
-
-        /**
-         * All ops that need to be flipped to allow if default.
-         *
-         * Currently, only used by the restricted permissions logic.
-         *
-         * @see #syncPackages
-         */
-        private final @NonNull ArrayList<OpToChange> mOpsToAllowIfDefault = new ArrayList<>();
-
-        /**
          * All ops that need to be flipped to allow.
          *
          * @see #syncPackages
@@ -422,15 +401,6 @@
         private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();
 
         /**
-         * All ops that need to be flipped to ignore if default.
-         *
-         * Currently, only used by the restricted permissions logic.
-         *
-         * @see #syncPackages
-         */
-        private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfDefault = new ArrayList<>();
-
-        /**
          * All ops that need to be flipped to ignore.
          *
          * @see #syncPackages
@@ -438,6 +408,15 @@
         private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();
 
         /**
+         * All ops that need to be flipped to ignore if not allowed.
+         *
+         * Currently, only used by soft restricted permissions logic.
+         *
+         * @see #syncPackages
+         */
+        private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();
+
+        /**
          * All ops that need to be flipped to foreground.
          *
          * Currently, only used by the foreground/background permissions logic.
@@ -481,19 +460,6 @@
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
-            final int allowIfDefaultCount = mOpsToAllowIfDefault.size();
-            for (int i = 0; i < allowIfDefaultCount; i++) {
-                final OpToChange op = mOpsToAllowIfDefault.get(i);
-                if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
-                    continue;
-                }
-
-                boolean wasSet = setUidModeAllowedIfDefault(op.code, op.uid, op.packageName);
-                if (wasSet) {
-                    alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
-                }
-            }
-
             final int foregroundIfAllowedCount = mOpsToForegroundIfAllow.size();
             for (int i = 0; i < foregroundIfAllowedCount; i++) {
                 final OpToChange op = mOpsToForegroundIfAllow.get(i);
@@ -529,29 +495,18 @@
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
-            final int ignoreIfDefaultCount = mOpsToIgnoreIfDefault.size();
-            for (int i = 0; i < ignoreIfDefaultCount; i++) {
-                final OpToChange op = mOpsToIgnoreIfDefault.get(i);
+            final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
+            for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
+                final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                boolean wasSet = setUidModeIgnoredIfDefault(op.code, op.uid, op.packageName);
+                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
                 if (wasSet) {
                     alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
                 }
             }
-
-            final int defaultCount = mOpsToDefault.size();
-            for (int i = 0; i < defaultCount; i++) {
-                final OpToChange op = mOpsToDefault.get(i);
-                if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
-                    continue;
-                }
-
-                setUidModeDefault(op.code, op.uid, op.packageName);
-                alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
-            }
         }
 
         /**
@@ -573,60 +528,49 @@
                 return;
             }
 
-            final boolean applyRestriction =
-                    (mPackageManager.getPermissionFlags(permission, pkg.packageName,
-                    mContext.getUser()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-
-            if (permissionInfo.isHardRestricted()) {
-                if (opCode != OP_NONE) {
-                    if (applyRestriction) {
-                        mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode));
-                    } else {
-                        mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode));
+            if (opCode != OP_NONE) {
+                int permissionFlags = mPackageManager.getPermissionFlags(permission,
+                        pkg.packageName, mContext.getUser());
+                boolean isReviewRequired = (permissionFlags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
+                if (!isReviewRequired) {
+                    boolean isRevokedCompat =
+                            (permissionFlags & FLAG_PERMISSION_REVOKED_COMPAT) != 0;
+                    if (permissionInfo.isHardRestricted()) {
+                        boolean shouldApplyRestriction =
+                                (permissionFlags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                        if (isRevokedCompat || shouldApplyRestriction) {
+                            mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, opCode));
+                        } else {
+                            mOpsToAllow.add(new OpToChange(uid, pkg.packageName, opCode));
+                        }
+                    } else if (permissionInfo.isSoftRestricted()) {
+                        SoftRestrictedPermissionPolicy policy =
+                                SoftRestrictedPermissionPolicy.forPermission(mContext,
+                                        pkg.applicationInfo, mContext.getUser(), permission);
+                        if (!isRevokedCompat && policy.mayGrantPermission()) {
+                            mOpsToAllow.add(new OpToChange(uid, pkg.packageName, opCode));
+                        } else {
+                            mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, opCode));
+                        }
                     }
                 }
-            } else if (permissionInfo.isSoftRestricted()) {
-                final SoftRestrictedPermissionPolicy policy =
+            }
+
+            if (permissionInfo.isSoftRestricted()) {
+                SoftRestrictedPermissionPolicy policy =
                         SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
                                 mContext.getUser(), permission);
-
-                if (opCode != OP_NONE) {
-                    if (policy.canBeGranted()) {
-                        mOpsToAllowIfDefault.add(new OpToChange(uid, pkg.packageName, opCode));
+                int extraOpCode = policy.getExtraAppOpCode();
+                if (extraOpCode != OP_NONE) {
+                    if (policy.mayAllowExtraAppOp()) {
+                        mOpsToAllow.add(new OpToChange(uid, pkg.packageName, extraOpCode));
                     } else {
-                        mOpsToDefault.add(new OpToChange(uid, pkg.packageName, opCode));
-                    }
-                }
-
-                final int op = policy.resolveAppOp();
-                if (op != OP_NONE) {
-                    switch (policy.getDesiredOpMode()) {
-                        case MODE_DEFAULT:
-                            mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op));
-                            break;
-                        case MODE_ALLOWED:
-                            if (policy.shouldSetAppOpIfNotDefault()) {
-                                mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op));
-                            } else {
-                                mOpsToAllowIfDefault.add(
-                                        new OpToChange(uid, pkg.packageName, op));
-                            }
-                            break;
-                        case MODE_FOREGROUND:
-                            Slog.wtf(LOG_TAG,
-                                    "Setting appop to foreground is not implemented");
-                            break;
-                        case MODE_IGNORED:
-                            if (policy.shouldSetAppOpIfNotDefault()) {
-                                mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, op));
-                            } else {
-                                mOpsToIgnoreIfDefault.add(
-                                        new OpToChange(uid, pkg.packageName,
-                                                op));
-                            }
-                            break;
-                        case MODE_ERRORED:
-                            Slog.wtf(LOG_TAG, "Setting appop to errored is not implemented");
+                        if (policy.mayDenyExtraAppOpIfGranted()) {
+                            mOpsToIgnore.add(new OpToChange(uid, pkg.packageName, extraOpCode));
+                        } else {
+                            mOpsToIgnoreIfNotAllowed.add(new OpToChange(uid, pkg.packageName,
+                                    extraOpCode));
+                        }
                     }
                 }
             }
@@ -745,60 +689,51 @@
             }
         }
 
-        private boolean setUidModeAllowedIfDefault(int opCode, int uid,
-                @NonNull String packageName) {
-            return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_ALLOWED, packageName);
-        }
-
         private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
             setUidMode(opCode, uid, MODE_ALLOWED, packageName);
         }
 
         private boolean setUidModeForegroundIfAllow(int opCode, int uid,
                 @NonNull String packageName) {
-            return setUidModeIfMode(opCode, uid, MODE_ALLOWED, MODE_FOREGROUND, packageName);
+            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+                    opCode), uid, packageName);
+            if (currentMode == MODE_ALLOWED) {
+                mAppOpsManager.setUidMode(opCode, uid, MODE_FOREGROUND);
+                return true;
+            }
+            return false;
         }
 
         private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
             setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
         }
 
-        private boolean setUidModeIgnoredIfDefault(int opCode, int uid,
-                @NonNull String packageName) {
-            return setUidModeIfMode(opCode, uid, MODE_DEFAULT, MODE_IGNORED, packageName);
-        }
-
         private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
             setUidMode(opCode, uid, MODE_IGNORED, packageName);
         }
 
+        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
+                @NonNull String packageName) {
+            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
+                    opCode), uid, packageName);
+            if (currentMode != MODE_ALLOWED) {
+                if (currentMode != MODE_IGNORED) {
+                    mAppOpsManager.setUidMode(opCode, uid, MODE_IGNORED);
+                }
+                return true;
+            }
+            return false;
+        }
+
         private void setUidMode(int opCode, int uid, int mode,
                 @NonNull String packageName) {
             final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
                     .opToPublicName(opCode), uid, packageName);
-
             if (currentMode != mode) {
                 mAppOpsManager.setUidMode(opCode, uid, mode);
             }
         }
 
-        private boolean setUidModeIfMode(int opCode, int uid, int requiredModeBefore, int newMode,
-                @NonNull String packageName) {
-            final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager
-                    .opToPublicName(opCode), uid, packageName);
-
-            if (currentMode == requiredModeBefore) {
-                mAppOpsManager.setUidMode(opCode, uid, newMode);
-                return true;
-            }
-
-            return false;
-        }
-
-        private void setUidModeDefault(int opCode, int uid, String packageName) {
-            setUidMode(opCode, uid, MODE_DEFAULT, packageName);
-        }
-
         private class OpToChange {
             final int uid;
             final @NonNull String packageName;
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 34c9258..b0f22e4 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -18,9 +18,6 @@
 
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -36,7 +33,6 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.parsing.AndroidPackage;
 import android.os.Build;
 import android.os.UserHandle;
 
@@ -56,22 +52,7 @@
     private static final SoftRestrictedPermissionPolicy DUMMY_POLICY =
             new SoftRestrictedPermissionPolicy() {
                 @Override
-                public int resolveAppOp() {
-                    return OP_NONE;
-                }
-
-                @Override
-                public int getDesiredOpMode() {
-                    return MODE_DEFAULT;
-                }
-
-                @Override
-                public boolean shouldSetAppOpIfNotDefault() {
-                    return false;
-                }
-
-                @Override
-                public boolean canBeGranted() {
+                public boolean mayGrantPermission() {
                     return true;
                 }
             };
@@ -115,10 +96,8 @@
      * Get the policy for a soft restricted permission.
      *
      * @param context A context to use
-     * @param appInfo The application the permission belongs to. Can be {@code null}, but then
-     *                only {@link #resolveAppOp} will work.
-     * @param user The user the app belongs to. Can be {@code null}, but then only
-     *             {@link #resolveAppOp} will work.
+     * @param appInfo The application the permission belongs to.
+     * @param user The user the app belongs to.
      * @param permission The name of the permission
      *
      * @return The policy for this permission
@@ -131,82 +110,46 @@
             // where the restricted state allows the permission but only for accessing the medial
             // collections.
             case READ_EXTERNAL_STORAGE: {
-                final int flags;
-                final boolean applyRestriction;
                 final boolean isWhiteListed;
-                final boolean hasRequestedLegacyExternalStorage;
+                boolean shouldApplyRestriction;
                 final int targetSDK;
+                final boolean hasRequestedLegacyExternalStorage;
 
                 if (appInfo != null) {
                     PackageManager pm = context.getPackageManager();
-                    flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
-                    applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                    int flags = pm.getPermissionFlags(permission, appInfo.packageName, user);
                     isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
-
-                    boolean hasAnyRequestedLegacyExternalStorage =
-                            appInfo.hasRequestedLegacyExternalStorage();
-
-                    // hasRequestedLegacyExternalStorage is per package. To make sure two apps in
-                    // the same shared UID do not fight over what to set, always compute the
-                    // combined hasRequestedLegacyExternalStorage
-                    String[] uidPkgs = pm.getPackagesForUid(appInfo.uid);
-                    if (uidPkgs != null) {
-                        for (String uidPkg : uidPkgs) {
-                            if (!uidPkg.equals(appInfo.packageName)) {
-                                ApplicationInfo uidPkgInfo;
-                                try {
-                                    uidPkgInfo = pm.getApplicationInfoAsUser(uidPkg, 0, user);
-                                } catch (PackageManager.NameNotFoundException e) {
-                                    continue;
-                                }
-
-                                hasAnyRequestedLegacyExternalStorage |=
-                                        uidPkgInfo.hasRequestedLegacyExternalStorage();
-                            }
-                        }
-                    }
-
-                    hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
+                    hasRequestedLegacyExternalStorage = hasUidRequestedLegacyExternalStorage(
+                            appInfo.uid, context);
                 } else {
-                    flags = 0;
-                    applyRestriction = false;
                     isWhiteListed = false;
-                    hasRequestedLegacyExternalStorage = false;
+                    shouldApplyRestriction = false;
                     targetSDK = 0;
+                    hasRequestedLegacyExternalStorage = false;
                 }
 
+                // We have a check in PermissionPolicyService.PermissionToOpSynchroniser.setUidMode
+                // to prevent apps losing files in legacy storage, because we are holding the
+                // package manager lock here. If we ever remove this policy that check should be
+                // removed as well.
                 return new SoftRestrictedPermissionPolicy() {
                     @Override
-                    public int resolveAppOp() {
+                    public boolean mayGrantPermission() {
+                        return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
+                    }
+                    @Override
+                    public int getExtraAppOpCode() {
                         return OP_LEGACY_STORAGE;
                     }
-
                     @Override
-                    public int getDesiredOpMode() {
-                        if (applyRestriction) {
-                            return MODE_DEFAULT;
-                        } else if (hasRequestedLegacyExternalStorage) {
-                            return MODE_ALLOWED;
-                        } else {
-                            return MODE_IGNORED;
-                        }
+                    public boolean mayAllowExtraAppOp() {
+                        return !shouldApplyRestriction && hasRequestedLegacyExternalStorage;
                     }
-
                     @Override
-                    public boolean shouldSetAppOpIfNotDefault() {
-                        // Do not switch from allowed -> ignored as this would mean to retroactively
-                        // turn on isolated storage. This will make the app loose all its files.
-                        return getDesiredOpMode() != MODE_IGNORED;
-                    }
-
-                    @Override
-                    public boolean canBeGranted() {
-                        if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
-                            return true;
-                        } else {
-                            return false;
-                        }
+                    public boolean mayDenyExtraAppOpIfGranted() {
+                        return shouldApplyRestriction;
                     }
                 };
             }
@@ -226,22 +169,7 @@
 
                 return new SoftRestrictedPermissionPolicy() {
                     @Override
-                    public int resolveAppOp() {
-                        return OP_NONE;
-                    }
-
-                    @Override
-                    public int getDesiredOpMode() {
-                        return MODE_DEFAULT;
-                    }
-
-                    @Override
-                    public boolean shouldSetAppOpIfNotDefault() {
-                        return false;
-                    }
-
-                    @Override
-                    public boolean canBeGranted() {
+                    public boolean mayGrantPermission() {
                         return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
                     }
                 };
@@ -251,106 +179,51 @@
         }
     }
 
-    /**
-     * Get the policy for a soft restricted permission.
-     *
-     * @param context A context to use
-     * @param pkg The application the permission belongs to. Can be {@code null}, but then
-     *                only {@link #resolveAppOp} will work.
-     * @param user The user the app belongs to. Can be {@code null}, but then only
-     *             {@link #resolveAppOp} will work.
-     * @param permission The name of the permission
-     *
-     * @return The policy for this permission
-     */
-    public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
-            @Nullable AndroidPackage pkg, @Nullable UserHandle user,
-            @NonNull String permission) {
-        switch (permission) {
-            // Storage uses a special app op to decide the mount state and supports soft restriction
-            // where the restricted state allows the permission but only for accessing the medial
-            // collections.
-            case READ_EXTERNAL_STORAGE:
-            case WRITE_EXTERNAL_STORAGE: {
-                final int flags;
-                final boolean applyRestriction;
-                final boolean isWhiteListed;
-                final boolean hasRequestedLegacyExternalStorage;
-                final int targetSDK;
-
-                if (pkg != null) {
-                    flags = context.getPackageManager().getPermissionFlags(permission,
-                            pkg.getPackageName(), user);
-                    applyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-                    isWhiteListed = (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                    hasRequestedLegacyExternalStorage = pkg.hasRequestedLegacyExternalStorage();
-                    targetSDK = pkg.getTargetSdkVersion();
-                } else {
-                    flags = 0;
-                    applyRestriction = false;
-                    isWhiteListed = false;
-                    hasRequestedLegacyExternalStorage = false;
-                    targetSDK = 0;
-                }
-
-                return new SoftRestrictedPermissionPolicy() {
-                    @Override
-                    public int resolveAppOp() {
-                        return OP_LEGACY_STORAGE;
-                    }
-
-                    @Override
-                    public int getDesiredOpMode() {
-                        if (applyRestriction) {
-                            return MODE_DEFAULT;
-                        } else if (hasRequestedLegacyExternalStorage) {
-                            return MODE_ALLOWED;
-                        } else {
-                            return MODE_IGNORED;
-                        }
-                    }
-
-                    @Override
-                    public boolean shouldSetAppOpIfNotDefault() {
-                        // Do not switch from allowed -> ignored as this would mean to retroactively
-                        // turn on isolated storage. This will make the app loose all its files.
-                        return getDesiredOpMode() != MODE_IGNORED;
-                    }
-
-                    @Override
-                    public boolean canBeGranted() {
-                        if (isWhiteListed || targetSDK >= Build.VERSION_CODES.Q) {
-                            return true;
-                        } else {
-                            return false;
-                        }
-                    }
-                };
-            }
-            default:
-                return DUMMY_POLICY;
+    private static boolean hasUidRequestedLegacyExternalStorage(int uid, @NonNull Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (packageNames == null) {
+            return false;
         }
+        UserHandle user = UserHandle.getUserHandleForUid(uid);
+        for (String packageName : packageNames) {
+            ApplicationInfo applicationInfo;
+            try {
+                applicationInfo = packageManager.getApplicationInfoAsUser(packageName, 0, user);
+            } catch (PackageManager.NameNotFoundException e) {
+                continue;
+            }
+            if (applicationInfo.hasRequestedLegacyExternalStorage()) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
+     * @return If the permission can be granted
+     */
+    public abstract boolean mayGrantPermission();
+
+    /**
      * @return An app op to be changed based on the state of the permission or
      * {@link AppOpsManager#OP_NONE} if not app-op should be set.
      */
-    public abstract int resolveAppOp();
+    public int getExtraAppOpCode() {
+        return OP_NONE;
+    }
 
     /**
-     * @return The mode the {@link #resolveAppOp() app op} should be in.
+     * @return Whether the {@link #getExtraAppOpCode() app op} may be granted.
      */
-    public abstract @AppOpsManager.Mode int getDesiredOpMode();
+    public boolean mayAllowExtraAppOp() {
+        return false;
+    }
 
     /**
-     * @return If the {@link #resolveAppOp() app op} should be set even if the app-op is currently
-     * not {@link AppOpsManager#MODE_DEFAULT}.
+     * @return Whether the {@link #getExtraAppOpCode() app op} may be denied if was granted.
      */
-    public abstract boolean shouldSetAppOpIfNotDefault();
-
-    /**
-     * @return If the permission can be granted
-     */
-    public abstract boolean canBeGranted();
+    public boolean mayDenyExtraAppOpIfGranted() {
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 6b2f9da..59f086e 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -449,9 +449,9 @@
                 "Checking " + apps.size() + " opening apps (frozen="
                         + mService.mDisplayFrozen + " timeout="
                         + mDisplayContent.mAppTransition.isTimeout() + ")...");
-        final ScreenRotationAnimation screenRotationAnimation =
-                mService.mAnimator.getScreenRotationAnimationLocked(
-                        Display.DEFAULT_DISPLAY);
+
+        final ScreenRotationAnimation screenRotationAnimation = mService.mRoot.getDisplayContent(
+                Display.DEFAULT_DISPLAY).getRotationAnimation();
 
         if (!mDisplayContent.mAppTransition.isTimeout()) {
             // Imagine the case where we are changing orientation due to an app transition, but a
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 7557271a..a25aab2 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -20,7 +20,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.util.Slog;
 import android.view.Surface.OutOfResourcesException;
@@ -33,14 +32,15 @@
  * Four black surfaces put together to make a black frame.
  */
 public class BlackFrame {
-    class BlackSurface {
+    static class BlackSurface {
         final int left;
         final int top;
         final int layer;
         final SurfaceControl surface;
 
         BlackSurface(SurfaceControl.Transaction transaction, int layer,
-                int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
+                int l, int t, int r, int b, DisplayContent dc,
+                SurfaceControl surfaceControl) throws OutOfResourcesException {
             left = l;
             top = t;
             this.layer = layer;
@@ -50,7 +50,7 @@
             surface = dc.makeOverlay()
                     .setName("BlackSurface")
                     .setColorLayer()
-                    .setParent(null) // TODO: Work-around for b/69259549
+                    .setParent(surfaceControl)
                     .build();
             transaction.setWindowCrop(surface, w, h);
             transaction.setLayerStack(surface, dc.getDisplayId());
@@ -61,43 +61,12 @@
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
                             "  BLACK " + surface + ": CREATE layer=" + layer);
         }
-
-        void setAlpha(SurfaceControl.Transaction t, float alpha) {
-            t.setAlpha(surface, alpha);
-        }
-
-        void setMatrix(SurfaceControl.Transaction t, Matrix matrix) {
-            mTmpMatrix.setTranslate(left, top);
-            mTmpMatrix.postConcat(matrix);
-            mTmpMatrix.getValues(mTmpFloats);
-            t.setPosition(surface, mTmpFloats[Matrix.MTRANS_X],
-                    mTmpFloats[Matrix.MTRANS_Y]);
-            t.setMatrix(surface,
-                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
-                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-            if (false) {
-                Slog.i(TAG_WM, "Black Surface @ (" + left + "," + top + "): ("
-                        + mTmpFloats[Matrix.MTRANS_X] + ","
-                        + mTmpFloats[Matrix.MTRANS_Y] + ") matrix=["
-                        + mTmpFloats[Matrix.MSCALE_X] + ","
-                        + mTmpFloats[Matrix.MSCALE_Y] + "]["
-                        + mTmpFloats[Matrix.MSKEW_X] + ","
-                        + mTmpFloats[Matrix.MSKEW_Y] + "]");
-            }
-        }
-
-        void clearMatrix(SurfaceControl.Transaction t) {
-            t.setMatrix(surface, 1, 0, 0, 1);
-        }
     }
 
-    final Rect mOuterRect;
-    final Rect mInnerRect;
-    final Matrix mTmpMatrix = new Matrix();
-    final float[] mTmpFloats = new float[9];
-    final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
+    private final Rect mOuterRect;
+    private final Rect mInnerRect;
+    private final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
 
-    final boolean mForceDefaultOrientation;
     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
 
     public void printTo(String prefix, PrintWriter pw) {
@@ -114,12 +83,12 @@
     }
 
     public BlackFrame(Supplier<SurfaceControl.Transaction> factory, SurfaceControl.Transaction t,
-            Rect outer, Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation)
+            Rect outer, Rect inner, int layer, DisplayContent dc, boolean forceDefaultOrientation,
+            SurfaceControl surfaceControl)
             throws OutOfResourcesException {
         boolean success = false;
 
         mTransactionFactory = factory;
-        mForceDefaultOrientation = forceDefaultOrientation;
 
         // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
         // b/68253229
@@ -128,19 +97,20 @@
         try {
             if (outer.top < inner.top) {
                 mBlackSurfaces[0] = new BlackSurface(t, layer,
-                        outer.left, outer.top, inner.right, inner.top, dc);
+                        outer.left, outer.top, inner.right, inner.top, dc, surfaceControl);
             }
             if (outer.left < inner.left) {
                 mBlackSurfaces[1] = new BlackSurface(t, layer,
-                        outer.left, inner.top, inner.left, outer.bottom, dc);
+                        outer.left, inner.top, inner.left, outer.bottom, dc, surfaceControl);
             }
             if (outer.bottom > inner.bottom) {
                 mBlackSurfaces[2] = new BlackSurface(t, layer,
-                        inner.left, inner.bottom, outer.right, outer.bottom, dc);
+                        inner.left, inner.bottom, outer.right, outer.bottom, dc,
+                        surfaceControl);
             }
             if (outer.right > inner.right) {
                 mBlackSurfaces[3] = new BlackSurface(t, layer,
-                        inner.right, outer.top, outer.right, inner.bottom, dc);
+                        inner.right, outer.top, outer.right, inner.bottom, dc, surfaceControl);
             }
             success = true;
         } finally {
@@ -151,51 +121,17 @@
     }
 
     public void kill() {
-        if (mBlackSurfaces != null) {
-            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,
+        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,
                             "  BLACK " + mBlackSurfaces[i].surface + ": DESTROY");
-                    t.remove(mBlackSurfaces[i].surface);
-                    mBlackSurfaces[i] = null;
                 }
-            }
-            t.apply();
-        }
-    }
-
-    public void hide(SurfaceControl.Transaction t) {
-        if (mBlackSurfaces != null) {
-            for (int i=0; i<mBlackSurfaces.length; i++) {
-                if (mBlackSurfaces[i] != null) {
-                    t.hide(mBlackSurfaces[i].surface);
-                }
+                t.remove(mBlackSurfaces[i].surface);
+                mBlackSurfaces[i] = null;
             }
         }
-    }
-
-    public void setAlpha(SurfaceControl.Transaction t, float alpha) {
-        for (int i=0; i<mBlackSurfaces.length; i++) {
-            if (mBlackSurfaces[i] != null) {
-                mBlackSurfaces[i].setAlpha(t, alpha);
-            }
-        }
-    }
-
-    public void setMatrix(SurfaceControl.Transaction t, Matrix matrix) {
-        for (int i=0; i<mBlackSurfaces.length; i++) {
-            if (mBlackSurfaces[i] != null) {
-                mBlackSurfaces[i].setMatrix(t, matrix);
-            }
-        }
-    }
-
-    public void clearMatrix(SurfaceControl.Transaction t) {
-        for (int i=0; i<mBlackSurfaces.length; i++) {
-            if (mBlackSurfaces[i] != null) {
-                mBlackSurfaces[i].clearMatrix(t);
-            }
-        }
+        t.apply();
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6462744..755180e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -497,6 +497,8 @@
     /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
     final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
 
+    private ScreenRotationAnimation mScreenRotationAnimation;
+
     /**
      * We organize all top-level Surfaces in to the following layers.
      * mOverlayLayer contains a few Surfaces which are always on top of others
@@ -521,9 +523,6 @@
      */
     private int mDeferUpdateImeTargetCount;
 
-    /** Temporary float array to retrieve 3x3 matrix values. */
-    private final float[] mTmpFloats = new float[9];
-
     private MagnificationSpec mMagnificationSpec;
 
     private InputMonitor mInputMonitor;
@@ -1309,9 +1308,9 @@
     void applyRotationLocked(final int oldRotation, final int rotation) {
         mDisplayRotation.applyCurrentRotation(rotation);
         final boolean rotateSeamlessly = mDisplayRotation.isRotatingSeamlessly();
-        final ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
-                ? null : mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
         final Transaction transaction = getPendingTransaction();
+        ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
+                ? null : getRotationAnimation();
         // We need to update our screen size information to match the new rotation. If the rotation
         // has actually changed then this method will return true and, according to the comment at
         // the top of the method, the caller is obligated to call computeNewConfigurationLocked().
@@ -2393,6 +2392,7 @@
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
             mPointerEventDispatcher.dispose();
+            setRotationAnimation(null);
             mWmService.mAnimator.removeDisplayLocked(mDisplayId);
             mWindowingLayer.release();
             mOverlayLayer.release();
@@ -2557,6 +2557,17 @@
         return delta;
     }
 
+    public void setRotationAnimation(ScreenRotationAnimation screenRotationAnimation) {
+        if (mScreenRotationAnimation != null) {
+            mScreenRotationAnimation.kill();
+        }
+        mScreenRotationAnimation = screenRotationAnimation;
+    }
+
+    public ScreenRotationAnimation getRotationAnimation() {
+        return mScreenRotationAnimation;
+    }
+
     private static void createRotationMatrix(int rotation, float displayWidth, float displayHeight,
             Matrix outMatrix) {
         // For rotations without Z-ordering we don't need the target rectangle's position.
@@ -2619,8 +2630,7 @@
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
         proto.write(ROTATION, getRotation());
-        final ScreenRotationAnimation screenRotationAnimation =
-                mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
+        final ScreenRotationAnimation screenRotationAnimation = getRotationAnimation();
         if (screenRotationAnimation != null) {
             screenRotationAnimation.writeToProto(proto, SCREEN_ROTATION_ANIMATION);
         }
@@ -2732,6 +2742,16 @@
             }
         }
 
+        final ScreenRotationAnimation rotationAnimation = getRotationAnimation();
+        if (rotationAnimation != null) {
+            pw.print(subPrefix);
+            pw.println("  mScreenRotationAnimation:");
+            rotationAnimation.printTo("  ", pw);
+        } else if (dumpAll) {
+            pw.print(subPrefix);
+            pw.println("  no ScreenRotationAnimation ");
+        }
+
         pw.println();
 
         // Dump stack references
@@ -3741,7 +3761,7 @@
         convertCropForSurfaceFlinger(frame, rot, dw, dh);
 
         final ScreenRotationAnimation screenRotationAnimation =
-                mWmService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
+                mWmService.mRoot.getDisplayContent(DEFAULT_DISPLAY).getRotationAnimation();
         final boolean inRotation = screenRotationAnimation != null &&
                 screenRotationAnimation.isAnimating();
         if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
@@ -4666,20 +4686,7 @@
     void prepareSurfaces() {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
         try {
-            final ScreenRotationAnimation screenRotationAnimation =
-                    mWmService.mAnimator.getScreenRotationAnimationLocked(mDisplayId);
             final Transaction transaction = getPendingTransaction();
-            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
-                screenRotationAnimation.getEnterTransformation().getMatrix().getValues(mTmpFloats);
-                transaction.setMatrix(mWindowingLayer,
-                        mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
-                        mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-                transaction.setPosition(mWindowingLayer,
-                        mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y]);
-                transaction.setAlpha(mWindowingLayer,
-                        screenRotationAnimation.getEnterTransformation().getAlpha());
-            }
-
             super.prepareSurfaces();
 
             // TODO: Once we totally eliminate global transaction we will pass transaction in here
@@ -4909,6 +4916,10 @@
         return mWindowingLayer;
     }
 
+    SurfaceControl getOverlayLayer() {
+        return mOverlayLayer;
+    }
+
     /**
      * Updates the display's system gesture exclusion.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 4d188f4..f08b4fc 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -404,7 +404,7 @@
             }
 
             final ScreenRotationAnimation screenRotationAnimation =
-                    mService.mAnimator.getScreenRotationAnimationLocked(displayId);
+                    mDisplayContent.getRotationAnimation();
             if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
                 // Rotation updates cannot be performed while the previous rotation change animation
                 // is still in progress. Skip this update. We will try updating again after the
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index f8f6334..d5f403f 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -113,7 +113,7 @@
 
                     final WindowState callingWin = mService.windowForClientLocked(
                             null, window, false);
-                    if (callingWin == null) {
+                    if (callingWin == null || callingWin.cantReceiveTouchInput()) {
                         Slog.w(TAG_WM, "Bad requesting window " + window);
                         return null;  // !!! TODO: throw here?
                     }
@@ -167,8 +167,7 @@
                     final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
                     if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag");
 
-                    final SurfaceControl.Transaction transaction =
-                            callingWin.getPendingTransaction();
+                    final SurfaceControl.Transaction transaction = mDragState.mTransaction;
                     transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
                     transaction.setPosition(
                             surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY);
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 17daabf..3c61694 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -120,7 +120,7 @@
     // A surface used to catch input events for the drag-and-drop operation.
     SurfaceControl mInputSurface;
 
-    private final SurfaceControl.Transaction mTransaction;
+    final SurfaceControl.Transaction mTransaction;
 
     private final Rect mTmpClipRect = new Rect();
 
@@ -129,7 +129,6 @@
      * {@code true} when {@link #closeLocked()} is called.
      */
     private boolean mIsClosing;
-    IBinder mTransferTouchFromToken;
 
     DragState(WindowManagerService service, DragDropController controller, IBinder token,
             SurfaceControl surface, int flags, IBinder localWin) {
@@ -167,12 +166,11 @@
 
         mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
         mTransaction.setWindowCrop(mInputSurface, mTmpClipRect);
-        mTransaction.transferTouchFocus(mTransferTouchFromToken, h.token);
-        mTransferTouchFromToken = null;
 
-        // syncInputWindows here to ensure the input channel isn't removed before the transfer.
+        // syncInputWindows here to ensure the input window info is sent before the
+        // transferTouchFocus is called.
         mTransaction.syncInputWindows();
-        mTransaction.apply();
+        mTransaction.apply(true);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index cbaf098..c4bfe4b 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -27,6 +27,7 @@
 
 import android.content.Context;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -41,6 +42,34 @@
 
 import java.io.PrintWriter;
 
+/**
+ * This class handles the rotation animation when the device is rotated.
+ *
+ * <p>
+ * The screen rotation animation is composed of 4 different part:
+ * <ul>
+ * <li> The screenshot: <p>
+ *     A screenshot of the whole screen prior the change of orientation is taken to hide the
+ *     element resizing below. The screenshot is then animated to rotate and cross-fade to
+ *     the new orientation with the content in the new orientation.
+ *
+ * <li> The windows on the display: <p>y
+ *      Once the device is rotated, the screen and its content are in the new orientation. The
+ *      animation first rotate the new content into the old orientation to then be able to
+ *      animate to the new orientation
+ *
+ * <li> The exiting Blackframe: <p>
+ *     Because the change of orientation might change the width and height of the content (i.e
+ *     when rotating from portrait to landscape) we "crop" the new content using black frames
+ *     around the screenshot so the new content does not go beyond the screenshot's bounds
+ *
+ * <li> The entering Blackframe: <p>
+ *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
+ *     rotation animation is used and matches the new content size instead of the screenshot.
+ * </ul>
+ *
+ * Each part has its own Surface which are then animated by {@link SurfaceAnimator}s.
+ */
 class ScreenRotationAnimation {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ScreenRotationAnimation" : TAG_WM;
 
@@ -66,6 +95,8 @@
     private final Matrix mSnapshotFinalMatrix = new Matrix();
     private final Matrix mExitFrameFinalMatrix = new Matrix();
     private final WindowManagerService mService;
+    private SurfaceControl mEnterBlackFrameLayer;
+    private SurfaceControl mRotationLayer;
     private SurfaceControl mSurfaceControl;
     private BlackFrame mEnteringBlackFrame;
     private int mWidth, mHeight;
@@ -81,15 +112,14 @@
     // rotations.
     private Animation mRotateExitAnimation;
     private Animation mRotateEnterAnimation;
+    private Animation mRotateAlphaAnimation;
     private boolean mStarted;
     private boolean mAnimRunning;
     private boolean mFinishAnimReady;
     private long mFinishAnimStartTime;
     private boolean mForceDefaultOrientation;
     private BlackFrame mExitingBlackFrame;
-    private boolean mMoreRotateEnter;
-    private boolean mMoreRotateExit;
-    private long mHalfwayPoint;
+    private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
             boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
@@ -126,11 +156,23 @@
         mOriginalRotation = originalRotation;
         mOriginalWidth = originalWidth;
         mOriginalHeight = originalHeight;
+        mSurfaceRotationAnimationController = new SurfaceRotationAnimationController();
 
         final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
         try {
-            mSurfaceControl = displayContent.makeOverlay()
+            mRotationLayer = displayContent.makeOverlay()
+                    .setName("RotationLayer")
+                    .setContainerLayer()
+                    .build();
+
+            mEnterBlackFrameLayer = displayContent.makeOverlay()
+                    .setName("EnterBlackFrameLayer")
+                    .setContainerLayer()
+                    .build();
+
+            mSurfaceControl = displayContent.makeSurface(null)
                     .setName("ScreenshotSurface")
+                    .setParent(mRotationLayer)
                     .setBufferSize(mWidth, mHeight)
                     .setSecure(isSecure)
                     .build();
@@ -160,8 +202,10 @@
                 if (gb.containsSecureLayers()) {
                     t.setSecure(mSurfaceControl, true);
                 }
+                t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE);
                 t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
                 t.setAlpha(mSurfaceControl, 0);
+                t.show(mRotationLayer);
                 t.show(mSurfaceControl);
             } else {
                 Slog.w(TAG, "Unable to take screenshot of display " + displayId);
@@ -211,22 +255,26 @@
         return mSurfaceControl != null;
     }
 
-    private void setSnapshotTransform(SurfaceControl.Transaction t, Matrix matrix, float alpha) {
-        if (mSurfaceControl != null) {
-            matrix.getValues(mTmpFloats);
-            float x = mTmpFloats[Matrix.MTRANS_X];
-            float y = mTmpFloats[Matrix.MTRANS_Y];
-            if (mForceDefaultOrientation) {
-                mDisplayContent.getBounds(mCurrentDisplayRect);
-                x -= mCurrentDisplayRect.left;
-                y -= mCurrentDisplayRect.top;
-            }
-            t.setPosition(mSurfaceControl, x, y);
-            t.setMatrix(mSurfaceControl,
-                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
-                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
-            t.setAlpha(mSurfaceControl, alpha);
+    private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
+        if (mRotationLayer == null) {
+            return;
         }
+        matrix.getValues(mTmpFloats);
+        float x = mTmpFloats[Matrix.MTRANS_X];
+        float y = mTmpFloats[Matrix.MTRANS_Y];
+        if (mForceDefaultOrientation) {
+            mDisplayContent.getBounds(mCurrentDisplayRect);
+            x -= mCurrentDisplayRect.left;
+            y -= mCurrentDisplayRect.top;
+        }
+        t.setPosition(mRotationLayer, x, y);
+        t.setMatrix(mRotationLayer,
+                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+
+        t.setAlpha(mSurfaceControl, (float) 1.0);
+        t.setAlpha(mRotationLayer, (float) 1.0);
+        t.show(mRotationLayer);
     }
 
     public void printTo(String prefix, PrintWriter pw) {
@@ -237,7 +285,9 @@
         if (mExitingBlackFrame != null) {
             mExitingBlackFrame.printTo(prefix + "  ", pw);
         }
-        pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame);
+        pw.print(prefix);
+        pw.print("mEnteringBlackFrame=");
+        pw.println(mEnteringBlackFrame);
         if (mEnteringBlackFrame != null) {
             mEnteringBlackFrame.printTo(prefix + "  ", pw);
         }
@@ -283,7 +333,7 @@
         int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
         createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
 
-        setSnapshotTransform(t, mSnapshotInitialMatrix, 1.0f);
+        setRotationTransform(t, mSnapshotInitialMatrix);
     }
 
     /**
@@ -304,6 +354,9 @@
         // Figure out how the screen has moved from the original rotation.
         int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
 
+        mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.screen_rotate_alpha);
+
         final boolean customAnim;
         if (exitAnim != 0 && enterAnim != 0) {
             customAnim = true;
@@ -353,6 +406,8 @@
         mRotateExitAnimation.scaleCurrentDuration(animationScale);
         mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
         mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+        mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
+        mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
 
         if (!customAnim && mExitingBlackFrame == null) {
             try {
@@ -373,13 +428,12 @@
                     outer = mCurrentDisplayRect;
                     inner = mOriginalDisplayRect;
                 } else {
-                    outer = new Rect(-mOriginalWidth * 1, -mOriginalHeight * 1,
-                            mOriginalWidth * 2, mOriginalHeight * 2);
-                    inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
+                    outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2);
+                    inner = new Rect(0, 0, mWidth, mHeight);
                 }
                 mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
-                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
-                mExitingBlackFrame.setMatrix(t, mFrameInitialMatrix);
+                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation,
+                        mRotationLayer);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             }
@@ -387,16 +441,17 @@
 
         if (customAnim && mEnteringBlackFrame == null) {
             try {
-                Rect outer = new Rect(-finalWidth * 1, -finalHeight * 1,
+                Rect outer = new Rect(-finalWidth, -finalHeight,
                         finalWidth * 2, finalHeight * 2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
                 mEnteringBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
-                        SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
+                        SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false, mEnterBlackFrameLayer);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             }
         }
 
+        mSurfaceRotationAnimationController.startAnimation();
         return true;
     }
 
@@ -421,14 +476,31 @@
     }
 
     public void kill() {
+        if (mSurfaceRotationAnimationController != null) {
+            mSurfaceRotationAnimationController.cancel();
+            mSurfaceRotationAnimationController = null;
+        }
         if (mSurfaceControl != null) {
             if (SHOW_TRANSACTIONS ||
                     SHOW_SURFACE_ALLOC) {
                 Slog.i(TAG_WM,
                         "  FREEZE " + mSurfaceControl + ": DESTROY");
             }
-            mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
             mSurfaceControl = null;
+            SurfaceControl.Transaction t = mService.mTransactionFactory.get();
+            if (mRotationLayer != null) {
+                if (mRotationLayer.isValid()) {
+                    t.remove(mRotationLayer);
+                }
+                mRotationLayer = null;
+            }
+            if (mEnterBlackFrameLayer != null) {
+                if (mEnterBlackFrameLayer.isValid()) {
+                    t.remove(mEnterBlackFrameLayer);
+                }
+                mEnterBlackFrameLayer = null;
+            }
+            t.apply();
         }
         if (mExitingBlackFrame != null) {
             mExitingBlackFrame.kill();
@@ -446,124 +518,186 @@
             mRotateEnterAnimation.cancel();
             mRotateEnterAnimation = null;
         }
+        if (mRotateAlphaAnimation != null) {
+            mRotateAlphaAnimation.cancel();
+            mRotateAlphaAnimation = null;
+        }
     }
 
     public boolean isAnimating() {
-        return hasAnimations();
+        return mSurfaceRotationAnimationController != null
+                && mSurfaceRotationAnimationController.isAnimating();
     }
 
     public boolean isRotating() {
         return mCurRotation != mOriginalRotation;
     }
 
-    private boolean hasAnimations() {
-        return mRotateEnterAnimation != null || mRotateExitAnimation != null;
-    }
-
-    private boolean stepAnimation(long now) {
-        if (now > mHalfwayPoint) {
-            mHalfwayPoint = Long.MAX_VALUE;
-        }
-        if (mFinishAnimReady && mFinishAnimStartTime < 0) {
-            mFinishAnimStartTime = now;
-        }
-
-        mMoreRotateExit = false;
-        if (mRotateExitAnimation != null) {
-            mMoreRotateExit = mRotateExitAnimation.getTransformation(now,
-                    mRotateExitTransformation);
-        }
-
-        mMoreRotateEnter = false;
-        if (mRotateEnterAnimation != null) {
-            mMoreRotateEnter = mRotateEnterAnimation.getTransformation(now,
-                    mRotateEnterTransformation);
-        }
-
-        if (!mMoreRotateExit) {
-            if (mRotateExitAnimation != null) {
-                mRotateExitAnimation.cancel();
-                mRotateExitAnimation = null;
-                mRotateExitTransformation.clear();
-            }
-        }
-
-        if (!mMoreRotateEnter) {
-            if (mRotateEnterAnimation != null) {
-                mRotateEnterAnimation.cancel();
-                mRotateEnterAnimation = null;
-                mRotateEnterTransformation.clear();
-            }
-        }
-
-        mExitTransformation.set(mRotateExitTransformation);
-        mEnterTransformation.set(mRotateEnterTransformation);
-
-        final boolean more = mMoreRotateEnter || mMoreRotateExit
-                || !mFinishAnimReady;
-
-        mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
-
-        return more;
-    }
-
-    void updateSurfaces(SurfaceControl.Transaction t) {
-        if (!mStarted) {
-            return;
-        }
-
-        if (mSurfaceControl != null) {
-            if (!mMoreRotateExit) {
-                t.hide(mSurfaceControl);
-            }
-        }
-
-        if (mExitingBlackFrame != null) {
-            if (!mMoreRotateExit) {
-                mExitingBlackFrame.hide(t);
-            } else {
-                mExitFrameFinalMatrix.setConcat(mExitTransformation.getMatrix(),
-                        mFrameInitialMatrix);
-                mExitingBlackFrame.setMatrix(t, mExitFrameFinalMatrix);
-                if (mForceDefaultOrientation) {
-                    mExitingBlackFrame.setAlpha(t, mExitTransformation.getAlpha());
-                }
-            }
-        }
-
-        if (mEnteringBlackFrame != null) {
-            if (!mMoreRotateEnter) {
-                mEnteringBlackFrame.hide(t);
-            } else {
-                mEnteringBlackFrame.setMatrix(t, mEnterTransformation.getMatrix());
-            }
-        }
-
-        t.setEarlyWakeup();
-        setSnapshotTransform(t, mSnapshotFinalMatrix, mExitTransformation.getAlpha());
-    }
-
-    public boolean stepAnimationLocked(long now) {
-        if (!hasAnimations()) {
-            mFinishAnimReady = false;
-            return false;
-        }
-
-        if (!mAnimRunning) {
-            if (mRotateEnterAnimation != null) {
-                mRotateEnterAnimation.setStartTime(now);
-            }
-            if (mRotateExitAnimation != null) {
-                mRotateExitAnimation.setStartTime(now);
-            }
-            mAnimRunning = true;
-            mHalfwayPoint = now + mRotateEnterAnimation.getDuration() / 2;
-        }
-
-        return stepAnimation(now);
-    }
-
     public Transformation getEnterTransformation() {
         return mEnterTransformation;
     }
+
+    /**
+     * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
+     * SurfaceAnimationRunner}.
+     * <p>
+     * The rotation animation is divided into the following hierarchy:
+     * <ul>
+     * <li> A first rotation layer, containing the blackframes. This layer is animated by the
+     * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation.
+     *     <ul>
+     *         <li> A child layer containing the screenshot on which is added an animation of it's
+     *     alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li>
+     *     </ul>
+     * <li> A second rotation layer used when custom animations are passed in
+     * {@link ScreenRotationAnimation#startAnimation(
+     *     SurfaceControl.Transaction, long, float, int, int, int, int)}.
+     * </ul>
+     * <p>
+     * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
+     * this three {@link SurfaceControl}s which then delegates the animation to the
+     * {@link ScreenRotationAnimation}.
+     */
+    class SurfaceRotationAnimationController {
+        private SurfaceAnimator mDisplayAnimator;
+        private SurfaceAnimator mEnterBlackFrameAnimator;
+        private SurfaceAnimator mScreenshotRotationAnimator;
+        private SurfaceAnimator mRotateScreenAnimator;
+        private final Runnable mNoopCallback = () -> { // b/141177184
+        };
+
+        /**
+         * Start the rotation animation of the display and the screenshot on the
+         * {@link SurfaceAnimationRunner}.
+         */
+        void startAnimation() {
+            mRotateScreenAnimator = startScreenshotAlphaAnimation();
+            mDisplayAnimator = startDisplayRotation();
+            if (mExitingBlackFrame != null) {
+                mScreenshotRotationAnimator = startScreenshotRotationAnimation();
+            }
+            if (mEnteringBlackFrame != null) {
+                mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
+            }
+        }
+
+        private SimpleSurfaceAnimatable.Builder initializeBuilder() {
+            return new SimpleSurfaceAnimatable.Builder()
+                    .setPendingTransactionSupplier(mDisplayContent::getPendingTransaction)
+                    .setCommitTransactionRunnable(mDisplayContent::commitPendingTransaction)
+                    .setAnimationLeashSupplier(mDisplayContent::makeOverlay);
+        }
+
+        private SurfaceAnimator startDisplayRotation() {
+            return startAnimation(initializeBuilder()
+                            .setAnimationLeashParent(mDisplayContent.getSurfaceControl())
+                            .setSurfaceControl(mDisplayContent.getWindowingLayer())
+                            .setParentSurfaceControl(mDisplayContent.getSurfaceControl())
+                            .setWidth(mDisplayContent.getSurfaceWidth())
+                            .setHeight(mDisplayContent.getSurfaceHeight())
+                            .build(),
+                    createWindowAnimationSpec(mRotateEnterAnimation),
+                    this::cancel);
+        }
+
+        private SurfaceAnimator startScreenshotAlphaAnimation() {
+            return startAnimation(initializeBuilder()
+                            .setSurfaceControl(mSurfaceControl)
+                            .setAnimationLeashParent(mRotationLayer)
+                            .setWidth(mWidth)
+                            .setHeight(mHeight)
+                            .build(),
+                    createWindowAnimationSpec(mRotateAlphaAnimation),
+                    mNoopCallback);
+        }
+
+        private SurfaceAnimator startEnterBlackFrameAnimation() {
+            return startAnimation(initializeBuilder()
+                            .setSurfaceControl(mEnterBlackFrameLayer)
+                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
+                            .build(),
+                    createWindowAnimationSpec(mRotateEnterAnimation),
+                    mNoopCallback);
+        }
+
+        private SurfaceAnimator startScreenshotRotationAnimation() {
+            return startAnimation(initializeBuilder()
+                            .setSurfaceControl(mRotationLayer)
+                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
+                            .build(),
+                    createWindowAnimationSpec(mRotateExitAnimation),
+                    this::onAnimationEnd);
+        }
+
+        private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
+            return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
+                    false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
+        }
+
+        /**
+         * Start an animation defined by animationSpec on a new {@link SurfaceAnimator}.
+         *
+         * @param animatable The animatable used for the animation.
+         * @param animationSpec                The spec of the animation.
+         * @param animationFinishedCallback    Callback passed to the {@link SurfaceAnimator} and
+         *                                    called when the animation finishes.
+         * @return The newly created {@link SurfaceAnimator} that as been started.
+         */
+        private SurfaceAnimator startAnimation(
+                SurfaceAnimator.Animatable animatable,
+                LocalAnimationAdapter.AnimationSpec animationSpec,
+                Runnable animationFinishedCallback) {
+            SurfaceAnimator animator = new SurfaceAnimator(
+                    animatable, animationFinishedCallback, mService);
+
+            LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
+                    animationSpec, mService.mSurfaceAnimationRunner);
+
+            animator.startAnimation(mDisplayContent.getPendingTransaction(),
+                    localAnimationAdapter, false);
+            return animator;
+        }
+
+        private void onAnimationEnd() {
+            mEnterBlackFrameAnimator = null;
+            mScreenshotRotationAnimator = null;
+            mRotateScreenAnimator = null;
+            mService.mAnimator.mBulkUpdateParams |= WindowSurfacePlacer.SET_UPDATE_ROTATION;
+            kill();
+            mService.updateRotation(false, false);
+            AccessibilityController accessibilityController = mService.mAccessibilityController;
+
+            if (accessibilityController != null) {
+                // We just finished rotation animation which means we did not
+                // announce the rotation and waited for it to end, announce now.
+                accessibilityController.onRotationChangedLocked(mDisplayContent);
+            }
+        }
+
+        public void cancel() {
+            if (mEnterBlackFrameAnimator != null) {
+                mEnterBlackFrameAnimator.cancelAnimation();
+            }
+
+            if (mScreenshotRotationAnimator != null) {
+                mScreenshotRotationAnimator.cancelAnimation();
+            }
+
+            if (mRotateScreenAnimator != null) {
+                mRotateScreenAnimator.cancelAnimation();
+            }
+
+            if (mDisplayAnimator != null) {
+                mDisplayAnimator.cancelAnimation();
+            }
+        }
+
+        public boolean isAnimating() {
+            return mDisplayAnimator != null && mDisplayAnimator.isAnimating()
+                    || mEnterBlackFrameAnimator != null && mEnterBlackFrameAnimator.isAnimating()
+                    || mRotateScreenAnimator != null && mRotateScreenAnimator.isAnimating()
+                    || mScreenshotRotationAnimator != null
+                    && mScreenshotRotationAnimator.isAnimating();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
new file mode 100644
index 0000000..bf5d5e2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SimpleSurfaceAnimatable.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.SurfaceControl;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * An implementation of {@link SurfaceAnimator.Animatable} that is instantiated
+ * using a builder pattern for more convenience over reimplementing the whole interface.
+ * <p>
+ * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance of this class.
+ *
+ * @see com.android.server.wm.SurfaceAnimator.Animatable
+ */
+public class SimpleSurfaceAnimatable implements SurfaceAnimator.Animatable {
+    private final int mWidth;
+    private final int mHeight;
+    private final boolean mShouldDeferAnimationFinish;
+    private final SurfaceControl mAnimationLeashParent;
+    private final SurfaceControl mSurfaceControl;
+    private final SurfaceControl mParentSurfaceControl;
+    private final Runnable mCommitTransactionRunnable;
+    private final Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
+    private final Supplier<SurfaceControl.Transaction> mPendingTransaction;
+    private final BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated;
+    private final Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost;
+    private final Consumer<Runnable> mOnAnimationFinished;
+
+    /**
+     * Use {@link SimpleSurfaceAnimatable.Builder} to create a new instance.
+     */
+    private SimpleSurfaceAnimatable(Builder builder) {
+        mWidth = builder.mWidth;
+        mHeight = builder.mHeight;
+        mShouldDeferAnimationFinish = builder.mShouldDeferAnimationFinish;
+        mAnimationLeashParent = builder.mAnimationLeashParent;
+        mSurfaceControl = builder.mSurfaceControl;
+        mParentSurfaceControl = builder.mParentSurfaceControl;
+        mCommitTransactionRunnable = builder.mCommitTransactionRunnable;
+        mAnimationLeashFactory = builder.mAnimationLeashFactory;
+        mOnAnimationLeashCreated = builder.mOnAnimationLeashCreated;
+        mOnAnimationLeashLost = builder.mOnAnimationLeashLost;
+        mPendingTransaction = builder.mPendingTransactionSupplier;
+        mOnAnimationFinished = builder.mOnAnimationFinished;
+    }
+
+    @NonNull
+    @Override
+    public SurfaceControl.Transaction getPendingTransaction() {
+        return mPendingTransaction.get();
+    }
+
+    @Override
+    public void commitPendingTransaction() {
+        mCommitTransactionRunnable.run();
+    }
+
+    @Override
+    public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) {
+        if (mOnAnimationLeashCreated != null) {
+            mOnAnimationLeashCreated.accept(t, leash);
+        }
+
+    }
+
+    @Override
+    public void onAnimationLeashLost(SurfaceControl.Transaction t) {
+        if (mOnAnimationLeashLost != null) {
+            mOnAnimationLeashLost.accept(t);
+        }
+    }
+
+    @Override
+    @NonNull
+    public SurfaceControl.Builder makeAnimationLeash() {
+        return mAnimationLeashFactory.get();
+    }
+
+    @Override
+    public SurfaceControl getAnimationLeashParent() {
+        return mAnimationLeashParent;
+    }
+
+    @Override
+    @Nullable
+    public SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    @Override
+    public SurfaceControl getParentSurfaceControl() {
+        return mParentSurfaceControl;
+    }
+
+    @Override
+    public int getSurfaceWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getSurfaceHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+        if (mOnAnimationFinished != null) {
+            mOnAnimationFinished.accept(endDeferFinishCallback);
+        }
+        return mShouldDeferAnimationFinish;
+    }
+
+    /**
+     * Builder class to create a {@link SurfaceAnimator.Animatable} without having to
+     * create a new class that implements the interface.
+     */
+    static class Builder {
+        private int mWidth = -1;
+        private int mHeight = -1;
+        private boolean mShouldDeferAnimationFinish = false;
+
+        @Nullable
+        private SurfaceControl mAnimationLeashParent = null;
+
+        @Nullable
+        private SurfaceControl mSurfaceControl = null;
+
+        @Nullable
+        private SurfaceControl mParentSurfaceControl = null;
+        private Runnable mCommitTransactionRunnable;
+
+        @Nullable
+        private BiConsumer<SurfaceControl.Transaction, SurfaceControl> mOnAnimationLeashCreated =
+                null;
+
+        @Nullable
+        private Consumer<SurfaceControl.Transaction> mOnAnimationLeashLost = null;
+
+        @Nullable
+        private Consumer<Runnable> mOnAnimationFinished = null;
+
+        @NonNull
+        private Supplier<SurfaceControl.Transaction> mPendingTransactionSupplier;
+
+        @NonNull
+        private Supplier<SurfaceControl.Builder> mAnimationLeashFactory;
+
+        /**
+         * Set the runnable to be called when
+         * {@link SurfaceAnimator.Animatable#commitPendingTransaction()}
+         * is called.
+         *
+         * @see SurfaceAnimator.Animatable#commitPendingTransaction()
+         */
+        public SimpleSurfaceAnimatable.Builder setCommitTransactionRunnable(
+                @NonNull Runnable commitTransactionRunnable) {
+            mCommitTransactionRunnable = commitTransactionRunnable;
+            return this;
+        }
+
+        /**
+         * Set the callback called when
+         * {@link SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
+         * SurfaceControl)} is called
+         *
+         * @see SurfaceAnimator.Animatable#onAnimationLeashCreated(SurfaceControl.Transaction,
+         * SurfaceControl)
+         */
+        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashCreated(
+                @Nullable BiConsumer<SurfaceControl.Transaction, SurfaceControl>
+                        onAnimationLeashCreated) {
+            mOnAnimationLeashCreated = onAnimationLeashCreated;
+            return this;
+        }
+
+        /**
+         * Set the callback called when
+         * {@link SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)}
+         * (SurfaceControl.Transaction, SurfaceControl)} is called
+         *
+         * @see SurfaceAnimator.Animatable#onAnimationLeashLost(SurfaceControl.Transaction)
+         */
+        public SimpleSurfaceAnimatable.Builder setOnAnimationLeashLost(
+                @Nullable Consumer<SurfaceControl.Transaction> onAnimationLeashLost) {
+            mOnAnimationLeashLost = onAnimationLeashLost;
+            return this;
+        }
+
+        /**
+         * @see SurfaceAnimator.Animatable#getPendingTransaction()
+         */
+        public Builder setPendingTransactionSupplier(
+                @NonNull Supplier<SurfaceControl.Transaction> pendingTransactionSupplier) {
+            mPendingTransactionSupplier = pendingTransactionSupplier;
+            return this;
+        }
+
+        /**
+         * Set the {@link Supplier} responsible for creating a new animation leash.
+         *
+         * @see SurfaceAnimator.Animatable#makeAnimationLeash()
+         */
+        public SimpleSurfaceAnimatable.Builder setAnimationLeashSupplier(
+                @NonNull Supplier<SurfaceControl.Builder> animationLeashFactory) {
+            mAnimationLeashFactory = animationLeashFactory;
+            return this;
+        }
+
+        /**
+         * @see SurfaceAnimator.Animatable#getAnimationLeashParent()
+         */
+        public SimpleSurfaceAnimatable.Builder setAnimationLeashParent(
+                SurfaceControl animationLeashParent) {
+            mAnimationLeashParent = animationLeashParent;
+            return this;
+        }
+
+        /**
+         * @see SurfaceAnimator.Animatable#getSurfaceControl()
+         */
+        public SimpleSurfaceAnimatable.Builder setSurfaceControl(
+                @NonNull SurfaceControl surfaceControl) {
+            mSurfaceControl = surfaceControl;
+            return this;
+        }
+
+        /**
+         * @see SurfaceAnimator.Animatable#getParentSurfaceControl()
+         */
+        public SimpleSurfaceAnimatable.Builder setParentSurfaceControl(
+                SurfaceControl parentSurfaceControl) {
+            mParentSurfaceControl = parentSurfaceControl;
+            return this;
+        }
+
+        /**
+         * Default to -1.
+         *
+         * @see SurfaceAnimator.Animatable#getSurfaceWidth()
+         */
+        public SimpleSurfaceAnimatable.Builder setWidth(int width) {
+            mWidth = width;
+            return this;
+        }
+
+        /**
+         * Default to -1.
+         *
+         * @see SurfaceAnimator.Animatable#getSurfaceHeight()
+         */
+        public SimpleSurfaceAnimatable.Builder setHeight(int height) {
+            mHeight = height;
+            return this;
+        }
+
+        /**
+         * Set the value returned by
+         * {@link SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}.
+         *
+         * @param onAnimationFinish will be called with the runnable to execute when the animation
+         *                          needs to be finished.
+         * @see SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)
+         */
+        public SimpleSurfaceAnimatable.Builder setShouldDeferAnimationFinish(
+                boolean shouldDeferAnimationFinish,
+                @Nullable Consumer<Runnable> onAnimationFinish) {
+            mShouldDeferAnimationFinish = shouldDeferAnimationFinish;
+            mOnAnimationFinished = onAnimationFinish;
+            return this;
+        }
+
+        public SurfaceAnimator.Animatable build() {
+            if (mPendingTransactionSupplier == null) {
+                throw new IllegalArgumentException("mPendingTransactionSupplier cannot be null");
+            }
+            if (mAnimationLeashFactory == null) {
+                throw new IllegalArgumentException("mAnimationLeashFactory cannot be null");
+            }
+            if (mCommitTransactionRunnable == null) {
+                throw new IllegalArgumentException("mCommitTransactionRunnable cannot be null");
+            }
+            if (mSurfaceControl == null) {
+                throw new IllegalArgumentException("mSurfaceControl cannot be null");
+            }
+            return new SimpleSurfaceAnimatable(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 42866f9..fc8a27d 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,6 +50,7 @@
 import android.view.InputEvent;
 import android.view.InputWindowHandle;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -302,7 +303,8 @@
         mDisplayContent.getDisplayRotation().pause();
 
         // Notify InputMonitor to take mDragWindowHandle.
-        mDisplayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
+        mDisplayContent.getInputMonitor().updateInputWindowsImmediately();
+        new SurfaceControl.Transaction().syncInputWindows().apply(true);
 
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 2441954..56b3bba 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -24,7 +24,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Slog;
@@ -51,7 +50,6 @@
     private @Nullable TaskPositioner mTaskPositioner;
 
     private final Rect mTmpClipRect = new Rect();
-    private IBinder mTransferTouchFromToken;
 
     boolean isPositioningLocked() {
         return mTaskPositioner != null;
@@ -104,8 +102,6 @@
 
         mTmpClipRect.set(0, 0, p.x, p.y);
         t.setWindowCrop(mInputSurface, mTmpClipRect);
-        t.transferTouchFocus(mTransferTouchFromToken, h.token);
-        mTransferTouchFromToken = null;
     }
 
     boolean startMovingTask(IWindow window, float startX, float startY) {
@@ -168,6 +164,7 @@
         mPositioningDisplay = displayContent;
 
         mTaskPositioner = TaskPositioner.create(mService);
+        mTaskPositioner.register(displayContent);
 
         // We need to grab the touch focus so that the touch events during the
         // resizing/scrolling are not sent to the app. 'win' is the main window
@@ -178,8 +175,12 @@
                 && displayContent.mCurrentFocus.mAppToken == win.mAppToken) {
             transferFocusFromWin = displayContent.mCurrentFocus;
         }
-        mTransferTouchFromToken = transferFocusFromWin.mInputChannel.getToken();
-        mTaskPositioner.register(displayContent);
+        if (!mInputManager.transferTouchFocus(
+                transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
+            Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
+            cleanUpTaskPositioner();
+            return false;
+        }
 
         mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
         return true;
diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
index d36ebf0..f291573 100644
--- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
+++ b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java
@@ -38,8 +38,8 @@
     private int mWidth;
     private int mHeight;
 
-    TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory, Task task,
-            SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
+    TaskScreenshotAnimatable(Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory,
+            Task task, SurfaceControl.ScreenshotGraphicBuffer screenshotBuffer) {
         GraphicBuffer buffer = screenshotBuffer == null
                 ? null : screenshotBuffer.getGraphicBuffer();
         mTask = task;
@@ -58,6 +58,8 @@
             surface.copyFrom(mSurfaceControl);
             surface.attachAndQueueBufferWithColorSpace(buffer, screenshotBuffer.getColorSpace());
             surface.release();
+            final float scale = 1.0f * mTask.getBounds().width() / mWidth;
+            mSurfaceControl.setMatrix(scale, 0, 0, scale);
         }
         getPendingTransaction().show(mSurfaceControl);
     }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c7916e8..1e206f6 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -21,7 +21,6 @@
 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.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 
 import android.content.Context;
 import android.os.Trace;
@@ -108,14 +107,6 @@
     }
 
     void removeDisplayLocked(final int displayId) {
-        final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
-        if (displayAnimator != null) {
-            if (displayAnimator.mScreenRotationAnimation != null) {
-                displayAnimator.mScreenRotationAnimation.kill();
-                displayAnimator.mScreenRotationAnimation = null;
-            }
-        }
-
         mDisplayContentsAnimators.delete(displayId);
     }
 
@@ -156,27 +147,6 @@
                 for (int i = 0; i < numDisplays; i++) {
                     final int displayId = mDisplayContentsAnimators.keyAt(i);
                     final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
-                    DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
-
-                    final ScreenRotationAnimation screenRotationAnimation =
-                            displayAnimator.mScreenRotationAnimation;
-                    if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
-                        if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
-                            setAnimating(true);
-                        } else {
-                            mBulkUpdateParams |= SET_UPDATE_ROTATION;
-                            screenRotationAnimation.kill();
-                            displayAnimator.mScreenRotationAnimation = null;
-
-                            // display.
-                            if (accessibilityController != null) {
-                                // We just finished rotation animation which means we did not
-                                // announce the rotation and waited for it to end, announce now.
-                                accessibilityController.onRotationChangedLocked(dc);
-                            }
-                        }
-                    }
-
                     // Update animations of all applications, including those
                     // associated with exiting/removed apps
                     dc.updateWindowsForAnimator();
@@ -188,12 +158,6 @@
                     final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
 
                     dc.checkAppWindowsReadyToShow();
-
-                    final ScreenRotationAnimation screenRotationAnimation =
-                            mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
-                    if (screenRotationAnimation != null) {
-                        screenRotationAnimation.updateSurfaces(mTransaction);
-                    }
                     orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
                     if (accessibilityController != null) {
                         accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
@@ -273,22 +237,14 @@
 
     public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
         final String subPrefix = "  " + prefix;
-        final String subSubPrefix = "  " + subPrefix;
 
         for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
             pw.print(prefix); pw.print("DisplayContentsAnimator #");
                     pw.print(mDisplayContentsAnimators.keyAt(i));
                     pw.println(":");
-            final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
             final DisplayContent dc =
                     mService.mRoot.getDisplayContent(mDisplayContentsAnimators.keyAt(i));
             dc.dumpWindowAnimators(pw, subPrefix);
-            if (displayAnimator.mScreenRotationAnimation != null) {
-                pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
-                displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw);
-            } else if (dumpAll) {
-                pw.print(subPrefix); pw.println("no ScreenRotationAnimation ");
-            }
             pw.println();
         }
 
@@ -322,23 +278,6 @@
         return displayAnimator;
     }
 
-    void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
-        final DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
-
-        if (animator != null) {
-            animator.mScreenRotationAnimation = animation;
-        }
-    }
-
-    ScreenRotationAnimation getScreenRotationAnimationLocked(int displayId) {
-        if (displayId < 0) {
-            return null;
-        }
-
-        DisplayContentsAnimator animator = getDisplayContentsAnimatorLocked(displayId);
-        return animator != null? animator.mScreenRotationAnimation : null;
-    }
-
     void requestRemovalOfReplacedWindows(WindowState win) {
         mRemoveReplacedWindows = true;
     }
@@ -358,7 +297,6 @@
     }
 
     private class DisplayContentsAnimator {
-        ScreenRotationAnimation mScreenRotationAnimation = null;
     }
 
     boolean isAnimating() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index aa2a964..abbd1ab 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -161,9 +161,8 @@
         default boolean registerInputChannel(
                 DragState state, Display display, InputManagerService service,
                 InputChannel source) {
-            state.mTransferTouchFromToken = source.getToken();
             state.register(display);
-            return true;
+            return service.transferTouchFocus(source, state.getInputChannel());
         }
 
         /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b4309c7..169e415 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5392,7 +5392,7 @@
         mExitAnimId = exitAnim;
         mEnterAnimId = enterAnim;
         ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(mFrozenDisplayId);
+                displayContent.getRotationAnimation();
         if (screenRotationAnimation != null) {
             screenRotationAnimation.kill();
         }
@@ -5404,8 +5404,7 @@
         screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
                 displayContent.getDisplayRotation().isFixedToUserRotation(), isSecure,
                 this);
-        mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
-                screenRotationAnimation);
+        displayContent.setRotationAnimation(screenRotationAnimation);
     }
 
     void stopFreezingDisplayLocked() {
@@ -5456,8 +5455,8 @@
 
         boolean updateRotation = false;
 
-        ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(displayId);
+        ScreenRotationAnimation screenRotationAnimation = displayContent == null ? null
+                : displayContent.getRotationAnimation();
         if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
             if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
@@ -5470,16 +5469,15 @@
                     getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                         displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                 mTransaction.apply();
-                scheduleAnimationLocked();
             } else {
                 screenRotationAnimation.kill();
-                mAnimator.setScreenRotationAnimationLocked(displayId, null);
+                displayContent.setRotationAnimation(null);
                 updateRotation = true;
             }
         } else {
             if (screenRotationAnimation != null) {
                 screenRotationAnimation.kill();
-                mAnimator.setScreenRotationAnimationLocked(displayId, null);
+                displayContent.setRotationAnimation(null);
             }
             updateRotation = true;
         }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index c676e72..8fccc8d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -673,9 +673,8 @@
     }
 
     void computeShownFrameLocked() {
-        final int displayId = mWin.getDisplayId();
         final ScreenRotationAnimation screenRotationAnimation =
-                mAnimator.getScreenRotationAnimationLocked(displayId);
+                mWin.getDisplayContent().getRotationAnimation();
         final boolean windowParticipatesInScreenRotationAnimation =
                 !mWin.mForceSeamlesslyRotate;
         final boolean screenAnimation = screenRotationAnimation != null
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 0e9da83..fb2fdab 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1570,6 +1570,27 @@
     im->setSystemUiVisibility(visibility);
 }
 
+static jboolean nativeTransferTouchFocus(JNIEnv* env,
+        jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    sp<InputChannel> fromChannel =
+            android_view_InputChannel_getInputChannel(env, fromChannelObj);
+    sp<InputChannel> toChannel =
+            android_view_InputChannel_getInputChannel(env, toChannelObj);
+
+    if (fromChannel == nullptr || toChannel == nullptr) {
+        return JNI_FALSE;
+    }
+
+    if (im->getInputManager()->getDispatcher()->
+            transferTouchFocus(fromChannel->getToken(), toChannel->getToken())) {
+        return JNI_TRUE;
+    } else {
+        return JNI_FALSE;
+    }
+}
+
 static void nativeSetPointerSpeed(JNIEnv* /* env */,
         jclass /* clazz */, jlong ptr, jint speed) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1763,6 +1784,8 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(JI)V",
             (void*) nativeSetSystemUiVisibility },
+    { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+            (void*) nativeTransferTouchFocus },
     { "nativeSetPointerSpeed", "(JI)V",
             (void*) nativeSetPointerSpeed },
     { "nativeSetShowTouches", "(JZ)V",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 479dd1e..c3ff285 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -126,6 +126,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.SecurityLog;
@@ -507,6 +508,7 @@
     private final OverlayPackagesProvider mOverlayPackagesProvider;
 
     private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
+    private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
 
     /**
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -2295,6 +2297,9 @@
                 policy = new DevicePolicyData(userHandle);
                 mUserData.append(userHandle, policy);
                 loadSettingsLocked(policy, userHandle);
+                if (userHandle == UserHandle.USER_SYSTEM) {
+                    mStateCache.setDeviceProvisioned(policy.mUserSetupComplete);
+                }
             }
             return policy;
         }
@@ -8958,6 +8963,8 @@
             pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
             pw.println();
             mPolicyCache.dump(pw);
+            pw.println();
+            mStateCache.dump(pw);
         }
     }
 
@@ -11169,6 +11176,9 @@
                 DevicePolicyData policy = getUserData(userHandle);
                 if (!policy.mUserSetupComplete) {
                     policy.mUserSetupComplete = true;
+                    if (userHandle == UserHandle.USER_SYSTEM) {
+                        mStateCache.setDeviceProvisioned(true);
+                    }
                     synchronized (getLockObject()) {
                         saveSettingsLocked(userHandle);
                     }
@@ -11481,6 +11491,12 @@
         protected DevicePolicyCache getDevicePolicyCache() {
             return mPolicyCache;
         }
+
+        @Override
+        protected DeviceStateCache getDeviceStateCache() {
+            return mStateCache;
+        }
+
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -13021,6 +13037,7 @@
                 Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
         DevicePolicyData policy = getUserData(userId);
         policy.mUserSetupComplete = isUserCompleted;
+        mStateCache.setDeviceProvisioned(isUserCompleted);
         synchronized (getLockObject()) {
             saveSettingsLocked(userId);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
new file mode 100644
index 0000000..c3cb9b0
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.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 com.android.server.devicepolicy;
+
+import android.app.admin.DeviceStateCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Implementation of {@link DeviceStateCache}, to which {@link DevicePolicyManagerService} pushes
+ * device state.
+ *
+ */
+public class DeviceStateCacheImpl extends DeviceStateCache {
+    /**
+     * Lock object. For simplicity we just always use this as the lock. We could use each object
+     * as a lock object to make it more fine-grained, but that'd make copy-paste error-prone.
+     */
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mIsDeviceProvisioned = false;
+
+    @Override
+    public boolean isDeviceProvisioned() {
+        return mIsDeviceProvisioned;
+    }
+
+    /** Update the device provisioned flag for USER_SYSTEM */
+    public void setDeviceProvisioned(boolean provisioned) {
+        synchronized (mLock) {
+            mIsDeviceProvisioned = provisioned;
+        }
+    }
+
+    /** Dump content */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Device state cache:");
+        pw.increaseIndent();
+        pw.println("Device provisioned: " + mIsDeviceProvisioned);
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 90d0c30..e93e17a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -141,7 +141,6 @@
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.signedconfig.SignedConfigService;
 import com.android.server.soundtrigger.SoundTriggerService;
-import com.android.server.stats.StatsCompanionService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
@@ -202,6 +201,8 @@
             "com.android.server.print.PrintManagerService";
     private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
             "com.android.server.companion.CompanionDeviceManagerService";
+    private static final String STATS_COMPANION_SERVICE_LIFECYCLE_CLASS =
+            "com.android.server.stats.StatsCompanionService$Lifecycle";
     private static final String USB_SERVICE_CLASS =
             "com.android.server.usb.UsbService$Lifecycle";
     private static final String MIDI_SERVICE_CLASS =
@@ -1875,7 +1876,7 @@
 
         // Statsd helper
         t.traceBegin("StartStatsCompanionService");
-        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        mSystemServiceManager.startService(STATS_COMPANION_SERVICE_LIFECYCLE_CLASS);
         t.traceEnd();
 
         // Incidentd and dumpstated helper
diff --git a/services/net/java/android/net/netlink/InetDiagMessage.java b/services/net/java/android/net/netlink/InetDiagMessage.java
index af9e601..31a2556 100644
--- a/services/net/java/android/net/netlink/InetDiagMessage.java
+++ b/services/net/java/android/net/netlink/InetDiagMessage.java
@@ -16,26 +16,23 @@
 
 package android.net.netlink;
 
-import static android.os.Process.INVALID_UID;
 import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
 import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
 import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.NETLINK_INET_DIAG;
 
-import android.os.Build;
-import android.os.Process;
+import android.net.util.SocketUtils;
 import android.system.ErrnoException;
 import android.util.Log;
 
 import java.io.FileDescriptor;
+import java.io.IOException;
 import java.io.InterruptedIOException;
-import java.net.DatagramSocket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetSocketAddress;
@@ -163,17 +160,25 @@
      */
     public static int getConnectionOwnerUid(int protocol, InetSocketAddress local,
                                             InetSocketAddress remote) {
+        int uid = INVALID_UID;
+        FileDescriptor fd = null;
         try {
-            final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
+            fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
             NetlinkSocket.connectToKernel(fd);
-
-            return lookupUid(protocol, local, remote, fd);
-
+            uid = lookupUid(protocol, local, remote, fd);
         } catch (ErrnoException | SocketException | IllegalArgumentException
                 | InterruptedIOException e) {
             Log.e(TAG, e.toString());
+        } finally {
+            if (fd != null) {
+                try {
+                    SocketUtils.closeSocket(fd);
+                } catch (IOException e) {
+                    Log.e(TAG, e.toString());
+                }
+            }
         }
-        return INVALID_UID;
+        return uid;
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1f5ebe4..7cece1f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -29,6 +29,7 @@
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.trust.TrustManager;
 import android.content.ComponentName;
 import android.content.pm.UserInfo;
@@ -37,6 +38,7 @@
 import android.os.IProgressListener;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.security.KeyStore;
@@ -91,6 +93,8 @@
     FakeGsiService mGsiService;
     PasswordSlotManagerTestable mPasswordSlotManager;
     RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+    UserManagerInternal mUserManagerInternal;
+    DeviceStateCache mDeviceStateCache;
     protected boolean mHasSecureLockScreen;
 
     @Override
@@ -108,6 +112,8 @@
         mGsiService = new FakeGsiService();
         mPasswordSlotManager = new PasswordSlotManagerTestable();
         mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
+        mUserManagerInternal = mock(UserManagerInternal.class);
+        mDeviceStateCache = mock(DeviceStateCache.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -144,7 +150,8 @@
         mAuthSecretService = mock(IAuthSecret.class);
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
-                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager);
+                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
+                mUserManagerInternal, mDeviceStateCache);
         when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
         mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
         installChildProfile(MANAGED_PROFILE_USER_ID);
@@ -172,6 +179,9 @@
         // Adding a fake Device Owner app which will enable escrow token support in LSS.
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
                 new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+        when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
+        when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
         mLocalService = LocalServices.getService(LockSettingsInternal.class);
     }
 
@@ -184,6 +194,7 @@
         when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
         when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
         when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+        when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
         return userInfo;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 5c67d04..65d6f45 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -19,12 +19,14 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.IActivityManager;
+import android.app.admin.DeviceStateCache;
 import android.content.Context;
 import android.hardware.authsecret.V1_0.IAuthSecret;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.security.KeyStore;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
@@ -44,15 +46,16 @@
         private LockPatternUtils mLockPatternUtils;
         private IStorageManager mStorageManager;
         private SyntheticPasswordManager mSpManager;
-        private IAuthSecret mAuthSecretService;
         private FakeGsiService mGsiService;
         private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+        private UserManagerInternal mUserManagerInternal;
+        private DeviceStateCache mDeviceStateCache;
 
         public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                 IActivityManager activityManager, LockPatternUtils lockPatternUtils,
                 IStorageManager storageManager, SyntheticPasswordManager spManager,
-                IAuthSecret authSecretService, FakeGsiService gsiService,
-                RecoverableKeyStoreManager recoverableKeyStoreManager) {
+                FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
             super(context);
             mLockSettingsStorage = storage;
             mKeyStore = keyStore;
@@ -62,6 +65,8 @@
             mSpManager = spManager;
             mGsiService = gsiService;
             mRecoverableKeyStoreManager = recoverableKeyStoreManager;
+            mUserManagerInternal = userManagerInternal;
+            mDeviceStateCache = deviceStateCache;
         }
 
         @Override
@@ -93,6 +98,10 @@
         public LockPatternUtils getLockPatternUtils() {
             return mLockPatternUtils;
         }
+        @Override
+        public DeviceStateCache getDeviceStateCache() {
+            return mDeviceStateCache;
+        }
 
         @Override
         public KeyStore getKeyStore() {
@@ -110,6 +119,11 @@
         }
 
         @Override
+        public UserManagerInternal getUserManagerInternal() {
+            return mUserManagerInternal;
+        }
+
+        @Override
         public boolean hasEnrolledBiometrics(int userId) {
             return false;
         }
@@ -134,10 +148,11 @@
             LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
             IStorageManager storageManager, IActivityManager mActivityManager,
             SyntheticPasswordManager spManager, IAuthSecret authSecretService,
-            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) {
+            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
         super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
-                storageManager, spManager, authSecretService, gsiService,
-                recoverableKeyStoreManager));
+                storageManager, spManager, gsiService,
+                recoverableKeyStoreManager, userManagerInternal, deviceStateCache));
         mGateKeeperService = gatekeeper;
         mAuthSecretService = authSecretService;
     }
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 127cf49..0776589 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.admin.PasswordMetrics;
 import android.os.RemoteException;
@@ -468,6 +469,18 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
+        when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
+        when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
+        try {
+            mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
+            fail("Escrow token should not be possible on unmanaged device");
+        } catch (SecurityException expected) { }
+    }
+
     public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
         final byte[] password = "password".getBytes();
         final byte[] pattern = "123654".getBytes();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index dcab78e..3d87223 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -16,9 +16,10 @@
 
 package com.android.server.notification;
 
-import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
 
 import android.app.NotificationManager.Policy;
 import android.content.ComponentName;
@@ -52,18 +53,18 @@
 
     @Test
     public void testPriorityOnlyMutingAllNotifications() {
-        ZenModeConfig config = getMutedNotificationsConfig();
-        assertEquals(true, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
+        ZenModeConfig config = getMutedRingerConfig();
+        assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
 
         config.allowReminders = true;
-        assertEquals(false, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
         config.allowReminders = false;
 
         config.areChannelsBypassingDnd = true;
-        assertEquals(false, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
         config.areChannelsBypassingDnd = false;
 
-        assertEquals(true, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
     }
 
     @Test
@@ -106,26 +107,26 @@
     @Test
     public void testPriorityOnlyMutingAll() {
         ZenModeConfig config = getMutedAllConfig();
-        assertEquals(true, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
-        assertEquals(true, ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
 
         config.allowReminders = true;
-        assertEquals(false, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
-        assertEquals(false, ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
         config.allowReminders = false;
 
         config.areChannelsBypassingDnd = true;
-        assertEquals(false, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
-        assertEquals(false, ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
         config.areChannelsBypassingDnd = false;
 
         config.allowAlarms = true;
-        assertEquals(true, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
-        assertEquals(false, ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+        assertFalse(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
         config.allowAlarms = false;
 
-        assertEquals(true, ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(config));
-        assertEquals(true, ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config));
+        assertTrue(ZenModeConfig.areAllZenBehaviorSoundsMuted(config));
     }
 
     @Test
@@ -200,14 +201,14 @@
         assertEquals(rule.zenMode, fromXml.zenMode);
     }
 
-    private ZenModeConfig getMutedNotificationsConfig() {
+    private ZenModeConfig getMutedRingerConfig() {
         ZenModeConfig config = new ZenModeConfig();
-        // Allow alarms, media, and system
+        // Allow alarms, media
         config.allowAlarms = true;
         config.allowMedia = true;
-        config.allowSystem = true;
 
-        // All notification sounds are not allowed
+        // All sounds that respect the ringer are not allowed
+        config.allowSystem = false;
         config.allowCalls = false;
         config.allowRepeatCallers = false;
         config.allowMessages = false;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 8936450..99771b9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -487,7 +487,6 @@
     public void testRingerAffectedStreamsPriorityOnly() {
         // in priority only mode:
         // ringtone, notification and system streams are affected by ringer mode
-        // UNLESS ringer is muted due to all the other priority only dnd sounds being muted
         mZenModeHelperSpy.mConfig.allowAlarms = true;
         mZenModeHelperSpy.mConfig.allowReminders = true;
         mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
@@ -503,8 +502,9 @@
         assertTrue((ringerModeAffectedStreams & (1 << AudioSystem.STREAM_ALARM)) == 0);
         assertTrue((ringerModeAffectedStreams & (1 << AudioSystem.STREAM_MUSIC)) == 0);
 
-        // special case: if ringer is muted (since all notification sounds cannot bypass)
-        // then system stream is not affected by ringer mode
+        // even when ringer is muted (since all ringer sounds cannot bypass DND),
+        // system stream is still affected by ringer mode
+        mZenModeHelperSpy.mConfig.allowSystem = false;
         mZenModeHelperSpy.mConfig.allowReminders = false;
         mZenModeHelperSpy.mConfig.allowCalls = false;
         mZenModeHelperSpy.mConfig.allowMessages = false;
@@ -519,7 +519,7 @@
         assertTrue((ringerMutedRingerModeAffectedStreams & (1 << AudioSystem.STREAM_NOTIFICATION))
                 != 0);
         assertTrue((ringerMutedRingerModeAffectedStreams & (1 << AudioSystem.STREAM_SYSTEM))
-                == 0);
+                != 0);
         assertTrue((ringerMutedRingerModeAffectedStreams & (1 << AudioSystem.STREAM_ALARM)) == 0);
         assertTrue((ringerMutedRingerModeAffectedStreams & (1 << AudioSystem.STREAM_MUSIC)) == 0);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 0110e94..bb80e5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -20,9 +20,11 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 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.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -124,6 +126,7 @@
         mDisplayContent = spy(mDisplayContent);
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
+        when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
 
         synchronized (mWm.mGlobalLock) {
             mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
@@ -177,6 +180,7 @@
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
 
+            assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
             mToken = mTarget.performDrag(
                     new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
                     data);
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 8d2a79b..2ad40f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -97,11 +97,6 @@
     }
 
     @Override
-    public SurfaceControl.Transaction transferTouchFocus(IBinder fromToken, IBinder toToken) {
-        return this;
-    }
-
-    @Override
     public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop,
             Rect destFrame, @Surface.Rotation int orientation) {
         return this;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 714d2f2..eb351b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -57,6 +58,10 @@
         assertNotNull(mWm.mTaskPositioningController);
         mTarget = mWm.mTaskPositioningController;
 
+        when(mWm.mInputManager.transferTouchFocus(
+                any(InputChannel.class),
+                any(InputChannel.class))).thenReturn(true);
+
         mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
         mWindow.mInputChannel = new InputChannel();
         synchronized (mWm.mGlobalLock) {
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 4fcda4d..7047498 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -302,6 +302,11 @@
     void setTestAutoModeApp(String packageName);
 
     /**
+     * @see TelecomServiceImpl#setSystemDialerPackage
+     */
+    void setSystemDialerPackage(in String packageName);
+
+    /**
      * @see TelecomServiceImpl#setTestDefaultDialer
      */
     void setTestDefaultDialer(in String packageName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b449578..1d5c18d 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1739,9 +1739,8 @@
             "allow_emergency_video_calls_bool";
 
     /**
-     * Flag indicating whether the carrier supports RCS presence indication for video calls.  When
-     * {@code true}, the carrier supports RCS presence indication for video calls.  When presence
-     * is supported, the device should use the
+     * Flag indicating whether the carrier supports RCS presence indication for
+     * User Capability Exchange (UCE).  When presence is supported, the device should use the
      * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} bit mask and set the
      * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit to indicate
      * whether each contact supports video calling.  The UI is made aware that presence is enabled
@@ -1752,6 +1751,12 @@
     public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
 
     /**
+     * Flag indicating whether the carrier supports RCS SIP OPTIONS indication for
+     * User Capability Exchange (UCE).
+     */
+    public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
+
+    /**
      * The duration in seconds that platform call and message blocking is disabled after the user
      * contacts emergency services. Platform considers values for below cases:
      *  1) 0 <= VALUE <= 604800(one week): the value will be used as the duration directly.
@@ -3435,6 +3440,7 @@
         sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
         sDefaults.putInt(KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT, 0);
         sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
+        sDefaults.putBoolean(KEY_USE_RCS_SIP_OPTIONS_BOOL, false);
         sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
         sDefaults.putInt(
                 KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 1ffed25..1e1e3da 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Binder;
 import android.os.Build;
@@ -366,6 +367,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
     public static final int LISTEN_OUTGOING_CALL_EMERGENCY_NUMBER           = 0x10000000;
 
@@ -378,6 +380,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     @RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
     public static final int LISTEN_OUTGOING_SMS_EMERGENCY_NUMBER            = 0x20000000;
 
@@ -869,6 +872,7 @@
      *                              to.
      * @hide
      */
+    @SystemApi
     public void onOutgoingEmergencyCall(@NonNull EmergencyNumber placedEmergencyNumber) {
         // default implementation empty
     }
@@ -879,6 +883,7 @@
      * @param sentEmergencyNumber the emergency number {@link EmergencyNumber} the SMS is sent to.
      * @hide
      */
+    @SystemApi
     public void onOutgoingEmergencySms(@NonNull EmergencyNumber sentEmergencyNumber) {
         // default implementation empty
     }
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index fe76b7c..d7a7af1 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -93,6 +93,10 @@
     void notifyActiveDataSubIdChanged(int activeDataSubId);
     void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
     void notifyEmergencyNumberList(in int phoneId, in int subId);
+    void notifyOutgoingEmergencyCall(in int phoneId, in int subId,
+            in EmergencyNumber emergencyNumber);
+    void notifyOutgoingEmergencySms(in int phoneId, in int subId,
+            in EmergencyNumber emergencyNumber);
     void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
             int callNetworkType);
     void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.java
new file mode 100644
index 0000000..022f798
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToAppTest.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.wm.flicker;
+
+import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test IME window closing back to app window transitions.
+ * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CloseImeAutoOpenWindowToAppTest extends CloseImeWindowToAppTest {
+
+    public CloseImeAutoOpenWindowToAppTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
+        mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Before
+    public void runTransition() {
+        run(editTextLoseFocusToApp((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation)
+                .includeJankyRuns().build());
+    }
+
+    @FlakyTest(bugId = 141458352)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_imeLayerBecomesInvisible() {
+        super.checkVisibility_imeLayerBecomesInvisible();
+    }
+
+    @FlakyTest(bugId = 141458352)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_imeAppLayerIsAlwaysVisible() {
+        super.checkVisibility_imeAppLayerIsAlwaysVisible();
+    }
+
+    @FlakyTest(bugId = 141458352)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_imeAppWindowIsAlwaysVisible() {
+        super.checkVisibility_imeAppWindowIsAlwaysVisible();
+    }
+
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java
new file mode 100644
index 0000000..d6f994b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeAutoOpenWindowToHomeTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker;
+
+import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+
+import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test IME window closing back to app window transitions.
+ * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class CloseImeAutoOpenWindowToHomeTest extends CloseImeWindowToHomeTest {
+
+    public CloseImeAutoOpenWindowToHomeTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
+        mTestApp = new ImeAppAutoFocusHelper(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @Before
+    public void runTransition() {
+        run(editTextLoseFocusToHome((ImeAppAutoFocusHelper) mTestApp, mUiDevice, mBeginRotation)
+                .includeJankyRuns().build());
+    }
+
+    @FlakyTest(bugId = 141458352)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_imeWindowBecomesInvisible() {
+        super.checkVisibility_imeWindowBecomesInvisible();
+    }
+
+    @FlakyTest(bugId = 141458352)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_imeLayerBecomesInvisible() {
+        super.checkVisibility_imeLayerBecomesInvisible();
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
index 9deb977..28da3af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToAppTest.java
@@ -17,37 +17,39 @@
 package com.android.server.wm.flicker;
 
 import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToApp;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-
-import android.platform.helpers.IAppHelper;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test IME window closing back to app window transitions.
  * To run this test: {@code atest FlickerTests:CloseImeWindowToAppTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class CloseImeWindowToAppTest extends FlickerTestBase {
+public class CloseImeWindowToAppTest extends NonRotationTestBase {
 
-    private static final String IME_WINDOW_TITLE = "InputMethod";
-    private IAppHelper mImeTestApp = new StandardAppHelper(
-            InstrumentationRegistry.getInstrumentation(),
-            "com.android.server.wm.flicker.testapp", "ImeApp");
+    static final String IME_WINDOW_TITLE = "InputMethod";
+
+    public CloseImeWindowToAppTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
+        mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+    }
 
     @Before
     public void runTransition() {
-        super.runTransition(editTextLoseFocusToApp(mUiDevice)
+        run(editTextLoseFocusToApp((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
                 .includeJankyRuns().build());
     }
 
@@ -63,20 +65,14 @@
     @Test
     public void checkVisibility_imeAppLayerIsAlwaysVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(mImeTestApp.getPackage())
+                .showsLayer(mTestApp.getPackage())
                 .forAllEntries());
     }
 
     @Test
     public void checkVisibility_imeAppWindowIsAlwaysVisible() {
         checkResults(result -> WmTraceSubject.assertThat(result)
-                .showsAppWindowOnTop(mImeTestApp.getPackage())
+                .showsAppWindowOnTop(mTestApp.getPackage())
                 .forAllEntries());
     }
-
-    @Test
-    public void checkCoveredRegion_noUncoveredRegions() {
-        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
-                getDisplayBounds()).forAllEntries());
-    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
index cce5a2a..fc6719e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CloseImeWindowToHomeTest.java
@@ -17,37 +17,39 @@
 package com.android.server.wm.flicker;
 
 import static com.android.server.wm.flicker.CommonTransitions.editTextLoseFocusToHome;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
-
-import android.platform.helpers.IAppHelper;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test IME window closing to home transitions.
  * To run this test: {@code atest FlickerTests:CloseImeWindowToHomeTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class CloseImeWindowToHomeTest extends FlickerTestBase {
+public class CloseImeWindowToHomeTest extends NonRotationTestBase {
 
-    private static final String IME_WINDOW_TITLE = "InputMethod";
-    private IAppHelper mImeTestApp = new StandardAppHelper(
-            InstrumentationRegistry.getInstrumentation(),
-            "com.android.server.wm.flicker.testapp", "ImeApp");
+    static final String IME_WINDOW_TITLE = "InputMethod";
+
+    public CloseImeWindowToHomeTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
+        mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+    }
 
     @Before
     public void runTransition() {
-        super.runTransition(editTextLoseFocusToHome(mUiDevice)
+        run(editTextLoseFocusToHome((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
                 .includeJankyRuns().build());
     }
 
@@ -72,24 +74,18 @@
     @Test
     public void checkVisibility_imeAppLayerBecomesInvisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(mImeTestApp.getPackage())
+                .showsLayer(mTestApp.getPackage())
                 .then()
-                .hidesLayer(mImeTestApp.getPackage())
+                .hidesLayer(mTestApp.getPackage())
                 .forAllEntries());
     }
 
     @Test
     public void checkVisibility_imeAppWindowBecomesInvisible() {
         checkResults(result -> WmTraceSubject.assertThat(result)
-                .showsAppWindowOnTop(mImeTestApp.getPackage())
+                .showsAppWindowOnTop(mTestApp.getPackage())
                 .then()
-                .hidesAppWindowOnTop(mImeTestApp.getPackage())
+                .hidesAppWindowOnTop(mTestApp.getPackage())
                 .forAllEntries());
     }
-
-    @Test
-    public void checkCoveredRegion_noUncoveredRegions() {
-        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
-                getDisplayBounds()).forAllEntries());
-    }
 }
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 1d44ea4..fd31aa5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonTransitions.java
@@ -37,10 +37,10 @@
 import android.util.Rational;
 import android.view.Surface;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
 import com.android.server.wm.flicker.helpers.AutomationUtils;
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
+import com.android.server.wm.flicker.helpers.PipAppHelper;
 
 /**
  * Collection of common transitions which can be used to test different apps or scenarios.
@@ -73,26 +73,17 @@
         }
     }
 
-    private static void clickEditTextWidget(UiDevice device, IAppHelper testApp) {
-        UiObject2 editText = device.findObject(By.res(testApp.getPackage(), "plain_text_input"));
-        editText.click();
-        sleep(500);
-    }
-
-    private static void clickEnterPipButton(UiDevice device, IAppHelper testApp) {
-        UiObject2 enterPipButton = device.findObject(By.res(testApp.getPackage(), "enter_pip"));
-        enterPipButton.click();
-        sleep(500);
-    }
-
     static TransitionBuilder openAppWarm(IAppHelper testApp, UiDevice
-            device) {
+            device, int beginRotation) {
         return TransitionRunner.newBuilder()
-                .withTag("OpenAppWarm_" + testApp.getLauncherName())
+                .withTag("OpenAppWarm_" + testApp.getLauncherName()
+                        + rotationToString(beginRotation))
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
+                .runBeforeAll(() -> setRotation(device, beginRotation))
                 .runBeforeAll(testApp::open)
                 .runBefore(device::pressHome)
                 .runBefore(device::waitForIdle)
+                .runBefore(() -> setRotation(device, beginRotation))
                 .run(testApp::open)
                 .runAfterAll(testApp::exit)
                 .runAfterAll(AutomationUtils::setDefaultWait)
@@ -127,16 +118,19 @@
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder getOpenAppCold(IAppHelper testApp,
-            UiDevice device) {
+    static TransitionBuilder openAppCold(IAppHelper testApp,
+            UiDevice device, int beginRotation) {
         return TransitionRunner.newBuilder()
-                .withTag("OpenAppCold_" + testApp.getLauncherName())
+                .withTag("OpenAppCold_" + testApp.getLauncherName()
+                        + rotationToString(beginRotation))
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
+                .runBeforeAll(() -> setRotation(device, beginRotation))
                 .runBefore(testApp::exit)
                 .runBefore(device::waitForIdle)
                 .run(testApp::open)
                 .runAfterAll(testApp::exit)
+                .runAfterAll(() -> setRotation(device, Surface.ROTATION_0))
                 .repeat(ITERATIONS);
     }
 
@@ -201,28 +195,31 @@
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder editTextSetFocus(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "ImeApp");
+    static TransitionBuilder editTextSetFocus(ImeAppHelper testApp, UiDevice device,
+            int beginRotation) {
         return TransitionRunner.newBuilder()
-                .withTag("editTextSetFocus_" + testApp.getLauncherName())
+                .withTag("editTextSetFocus_" + testApp.getLauncherName()
+                        + rotationToString(beginRotation))
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
+                .runBefore(() -> setRotation(device, beginRotation))
                 .runBefore(testApp::open)
-                .run(() -> clickEditTextWidget(device, testApp))
+                .run(() -> testApp.clickEditTextWidget(device))
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
-            UiDevice device, Rational startRatio, Rational stopRatio) {
+    static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, ImeAppHelper testAppBottom,
+            UiDevice device, int beginRotation, Rational startRatio, Rational stopRatio) {
         String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
                 + testAppBottom.getLauncherName() + "_"
                 + startRatio.toString().replace("/", ":") + "_to_"
-                + stopRatio.toString().replace("/", ":");
+                + stopRatio.toString().replace("/", ":") + "_"
+                + rotationToString(beginRotation);
         return TransitionRunner.newBuilder()
                 .withTag(testTag)
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
+                .runBeforeAll(() -> setRotation(device, beginRotation))
                 .runBeforeAll(() -> clearRecents(device))
                 .runBefore(testAppBottom::open)
                 .runBefore(device::pressHome)
@@ -234,6 +231,7 @@
                             By.res(device.getLauncherPackageName(), "snapshot"));
                     snapshot.click();
                 })
+                .runBefore(() -> testAppBottom.clickEditTextWidget(device))
                 .runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
                 .run(() -> AutomationUtils.resizeSplitScreen(device, stopRatio))
                 .runAfter(() -> exitSplitScreen(device))
@@ -243,74 +241,70 @@
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder editTextLoseFocusToHome(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "ImeApp");
+    static TransitionBuilder editTextLoseFocusToHome(ImeAppHelper testApp, UiDevice device,
+            int beginRotation) {
         return TransitionRunner.newBuilder()
-                .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName())
+                .withTag("editTextLoseFocusToHome_" + testApp.getLauncherName()
+                        + rotationToString(beginRotation))
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
+                .runBefore(() -> setRotation(device, beginRotation))
                 .runBefore(testApp::open)
-                .runBefore(() -> clickEditTextWidget(device, testApp))
+                .runBefore(() -> testApp.clickEditTextWidget(device))
                 .run(device::pressHome)
                 .run(device::waitForIdle)
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder editTextLoseFocusToApp(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "ImeApp");
+    static TransitionBuilder editTextLoseFocusToApp(ImeAppHelper testApp, UiDevice device,
+            int beginRotation) {
         return TransitionRunner.newBuilder()
-                .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName())
+                .withTag("editTextLoseFocusToApp_" + testApp.getLauncherName()
+                        + rotationToString(beginRotation))
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
+                .runBefore(() -> setRotation(device, beginRotation))
                 .runBefore(testApp::open)
-                .runBefore(() -> clickEditTextWidget(device, testApp))
+                .runBefore(() -> testApp.clickEditTextWidget(device))
                 .run(device::pressBack)
                 .run(device::waitForIdle)
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder enterPipMode(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "PipApp");
+    static TransitionBuilder enterPipMode(PipAppHelper testApp, UiDevice device) {
         return TransitionRunner.newBuilder()
                 .withTag("enterPipMode_" + testApp.getLauncherName())
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
                 .runBefore(testApp::open)
-                .run(() -> clickEnterPipButton(device, testApp))
+                .run(() -> testApp.clickEnterPipButton(device))
                 .runAfter(() -> closePipWindow(device))
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder exitPipModeToHome(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "PipApp");
+    static TransitionBuilder exitPipModeToHome(PipAppHelper testApp, UiDevice device) {
         return TransitionRunner.newBuilder()
                 .withTag("exitPipModeToHome_" + testApp.getLauncherName())
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
                 .runBefore(testApp::open)
-                .runBefore(() -> clickEnterPipButton(device, testApp))
+                .runBefore(() -> testApp.clickEnterPipButton(device))
                 .run(() -> closePipWindow(device))
                 .run(device::waitForIdle)
                 .runAfterAll(testApp::exit)
                 .repeat(ITERATIONS);
     }
 
-    static TransitionBuilder exitPipModeToApp(UiDevice device) {
-        IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "PipApp");
+    static TransitionBuilder exitPipModeToApp(PipAppHelper testApp, UiDevice device) {
         return TransitionRunner.newBuilder()
                 .withTag("exitPipModeToApp_" + testApp.getLauncherName())
                 .runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
                 .runBefore(device::pressHome)
                 .runBefore(testApp::open)
-                .runBefore(() -> clickEnterPipButton(device, testApp))
+                .runBefore(() -> testApp.clickEnterPipButton(device))
                 .run(() -> expandPipWindow(device))
                 .run(device::waitForIdle)
                 .runAfterAll(testApp::exit)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
index 9836655..8f0177c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/DebugTest.java
@@ -25,6 +25,9 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
+import com.android.server.wm.flicker.helpers.PipAppHelper;
+
 import org.junit.FixMethodOrder;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -44,23 +47,25 @@
     private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 
     /**
-     * atest FlickerTest:DebugTests#openAppCold
+     * atest FlickerTests:DebugTest#openAppCold
      */
     @Test
     public void openAppCold() {
-        CommonTransitions.getOpenAppCold(testApp, uiDevice).recordAllRuns().build().run();
+        CommonTransitions.openAppCold(testApp, uiDevice, Surface.ROTATION_0)
+                .recordAllRuns().build().run();
     }
 
     /**
-     * atest FlickerTest:DebugTests#openAppWarm
+     * atest FlickerTests:DebugTest#openAppWarm
      */
     @Test
     public void openAppWarm() {
-        CommonTransitions.openAppWarm(testApp, uiDevice).recordAllRuns().build().run();
+        CommonTransitions.openAppWarm(testApp, uiDevice, Surface.ROTATION_0)
+                .recordAllRuns().build().run();
     }
 
     /**
-     * atest FlickerTest:DebugTests#changeOrientationFromNaturalToLeft
+     * atest FlickerTests:DebugTest#changeOrientationFromNaturalToLeft
      */
     @Test
     public void changeOrientationFromNaturalToLeft() {
@@ -69,7 +74,7 @@
     }
 
     /**
-     * atest FlickerTest:DebugTests#closeAppWithBackKey
+     * atest FlickerTests:DebugTest#closeAppWithBackKey
      */
     @Test
     public void closeAppWithBackKey() {
@@ -77,7 +82,7 @@
     }
 
     /**
-     * atest FlickerTest:DebugTests#closeAppWithHomeKey
+     * atest FlickerTests:DebugTest#closeAppWithHomeKey
      */
     @Test
     public void closeAppWithHomeKey() {
@@ -85,7 +90,7 @@
     }
 
     /**
-     * atest FlickerTest:DebugTests#openAppToSplitScreen
+     * atest FlickerTests:DebugTest#openAppToSplitScreen
      */
     @Test
     public void openAppToSplitScreen() {
@@ -94,7 +99,7 @@
     }
 
     /**
-     * atest FlickerTest:DebugTests#splitScreenToLauncher
+     * atest FlickerTests:DebugTest#splitScreenToLauncher
      */
     @Test
     public void splitScreenToLauncher() {
@@ -104,70 +109,80 @@
     }
 
     /**
-     * atest FlickerTest:DebugTests#resizeSplitScreen
+     * atest FlickerTests:DebugTest#resizeSplitScreen
      */
     @Test
     public void resizeSplitScreen() {
-        IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "ImeApp");
-        CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3),
-                new Rational(2, 3)).includeJankyRuns().recordEachRun().build().run();
+        ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.resizeSplitScreen(testApp, bottomApp, uiDevice, Surface.ROTATION_0,
+                new Rational(1, 3), new Rational(2, 3))
+                .includeJankyRuns().recordEachRun().build().run();
     }
 
     // IME tests
 
     /**
-     * atest FlickerTest:DebugTests#editTextSetFocus
+     * atest FlickerTests:DebugTest#editTextSetFocus
      */
     @Test
     public void editTextSetFocus() {
-        CommonTransitions.editTextSetFocus(uiDevice).includeJankyRuns().recordEachRun()
+        ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.editTextSetFocus(testApp, uiDevice, Surface.ROTATION_0)
+                .includeJankyRuns().recordEachRun()
                 .build().run();
     }
 
     /**
-     * atest FlickerTest:DebugTests#editTextLoseFocusToHome
+     * atest FlickerTests:DebugTest#editTextLoseFocusToHome
      */
     @Test
     public void editTextLoseFocusToHome() {
-        CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun()
+        ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
+                .includeJankyRuns().recordEachRun()
                 .build().run();
     }
 
     /**
-     * atest FlickerTest:DebugTests#editTextLoseFocusToApp
+     * atest FlickerTests:DebugTest#editTextLoseFocusToApp
      */
     @Test
     public void editTextLoseFocusToApp() {
-        CommonTransitions.editTextLoseFocusToHome(uiDevice).includeJankyRuns().recordEachRun()
+        ImeAppHelper testApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.editTextLoseFocusToHome(testApp, uiDevice, Surface.ROTATION_0)
+                .includeJankyRuns().recordEachRun()
                 .build().run();
     }
 
     // PIP tests
 
     /**
-     * atest FlickerTest:DebugTests#enterPipMode
+     * atest FlickerTests:DebugTest#enterPipMode
      */
     @Test
     public void enterPipMode() {
-        CommonTransitions.enterPipMode(uiDevice).includeJankyRuns().recordEachRun().build().run();
-    }
-
-    /**
-     * atest FlickerTest:DebugTests#exitPipModeToHome
-     */
-    @Test
-    public void exitPipModeToHome() {
-        CommonTransitions.exitPipModeToHome(uiDevice).includeJankyRuns().recordEachRun()
+        PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.enterPipMode(testApp, uiDevice).includeJankyRuns().recordEachRun()
                 .build().run();
     }
 
     /**
-     * atest FlickerTest:DebugTests#exitPipModeToApp
+     * atest FlickerTests:DebugTest#exitPipModeToHome
+     */
+    @Test
+    public void exitPipModeToHome() {
+        PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.exitPipModeToHome(testApp, uiDevice).includeJankyRuns().recordEachRun()
+                .build().run();
+    }
+
+    /**
+     * atest FlickerTests:DebugTest#exitPipModeToApp
      */
     @Test
     public void exitPipModeToApp() {
-        CommonTransitions.exitPipModeToApp(uiDevice).includeJankyRuns().recordEachRun()
+        PipAppHelper testApp = new PipAppHelper(InstrumentationRegistry.getInstrumentation());
+        CommonTransitions.exitPipModeToApp(testApp, uiDevice).includeJankyRuns().recordEachRun()
                 .build().run();
     }
 }
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 6e8e0c3..883d59e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/FlickerTestBase.java
@@ -98,7 +98,7 @@
     /**
      * Runs a transition, returns a cached result if the transition has run before.
      */
-    void runTransition(TransitionRunner transition) {
+    void run(TransitionRunner transition) {
         if (transitionResults.containsKey(transition.getTestTag())) {
             mResults = transitionResults.get(transition.getTestTag());
             return;
@@ -111,6 +111,13 @@
     }
 
     /**
+     * Runs a transition, returns a cached result if the transition has run before.
+     */
+    void runTransition(TransitionRunner transition) {
+        run(transition);
+    }
+
+    /**
      * Goes through a list of transition results and checks assertions on each result.
      */
     void checkResults(Consumer<TransitionResult> assertion) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
new file mode 100644
index 0000000..54941dc
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/NonRotationTestBase.java
@@ -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 com.android.server.wm.flicker;
+
+import static android.view.Surface.rotationToString;
+
+import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
+
+import android.graphics.Rect;
+import android.view.Surface;
+
+import androidx.test.filters.FlakyTest;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class NonRotationTestBase extends FlickerTestBase {
+
+    int mBeginRotation;
+
+    public NonRotationTestBase(String beginRotationName, int beginRotation) {
+        this.mBeginRotation = beginRotation;
+    }
+
+    @Parameters(name = "{0}")
+    public static Collection<Object[]> getParams() {
+        int[] supportedRotations =
+                {Surface.ROTATION_0, Surface.ROTATION_90};
+        Collection<Object[]> params = new ArrayList<>();
+
+        for (int begin : supportedRotations) {
+            params.add(new Object[]{rotationToString(begin), begin});
+        }
+
+        return params;
+    }
+
+    @FlakyTest(bugId = 141361128)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkCoveredRegion_noUncoveredRegions() {
+        Rect displayBounds = getDisplayBounds(mBeginRotation);
+        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
+                displayBounds).forAllEntries());
+    }
+
+    @FlakyTest(bugId = 141361128)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_navBarLayerIsAlwaysVisible() {
+        checkResults(result -> LayersTraceSubject.assertThat(result)
+                .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
+    }
+
+    @FlakyTest(bugId = 141361128)
+    @Ignore("Waiting bug feedback")
+    @Test
+    public void checkVisibility_statusBarLayerIsAlwaysVisible() {
+        checkResults(result -> LayersTraceSubject.assertThat(result)
+                .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
index 8d99054..efdfaee 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppColdTest.java
@@ -16,14 +16,12 @@
 
 package com.android.server.wm.flicker;
 
-import static com.android.server.wm.flicker.CommonTransitions.getOpenAppCold;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
+import static com.android.server.wm.flicker.CommonTransitions.openAppCold;
 import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
@@ -31,36 +29,28 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test cold launch app from launcher.
  * To run this test: {@code atest FlickerTests:OpenAppColdTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppColdTest extends FlickerTestBase {
+public class OpenAppColdTest extends NonRotationTestBase {
 
-    public OpenAppColdTest() {
+    public OpenAppColdTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
         this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
-    }
-
-    @Test
-    public void checkVisibility_navBarWindowIsAlwaysVisible() {
-        checkResults(result -> assertThat(result)
-                .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_statusBarWindowIsAlwaysVisible() {
-        checkResults(result -> assertThat(result)
-                .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+        run(openAppCold(mTestApp, mUiDevice, mBeginRotation)
+                .includeJankyRuns().build());
     }
 
     @Test
@@ -72,6 +62,8 @@
                 .forAllEntries());
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
         checkResults(result -> assertThat(result)
@@ -83,26 +75,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 141235985)
-    @Ignore("Waiting bug feedback")
-    public void checkCoveredRegion_noUncoveredRegions() {
-        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
-                getDisplayBounds()).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_navBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_statusBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
-    @Test
     public void checkVisibility_wallpaperLayerBecomesInvisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
                 .showsLayer("Wallpaper")
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
index e8702c2..7ce6315 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenAppWarmTest.java
@@ -17,13 +17,11 @@
 package com.android.server.wm.flicker;
 
 import static com.android.server.wm.flicker.CommonTransitions.openAppWarm;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
 import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
@@ -31,36 +29,28 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test warm launch app.
  * To run this test: {@code atest FlickerTests:OpenAppWarmTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenAppWarmTest extends FlickerTestBase {
+public class OpenAppWarmTest extends NonRotationTestBase {
 
-    public OpenAppWarmTest() {
+    public OpenAppWarmTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
         this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
-    }
-
-    @Test
-    public void checkVisibility_navBarIsAlwaysVisible() {
-        checkResults(result -> assertThat(result)
-                .showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_statusBarIsAlwaysVisible() {
-        checkResults(result -> assertThat(result)
-                .showsAboveAppWindow(STATUS_BAR_WINDOW_TITLE).forAllEntries());
+        super.runTransition(openAppWarm(mTestApp, mUiDevice, mBeginRotation)
+                .includeJankyRuns().build());
     }
 
     @Test
@@ -72,6 +62,8 @@
                 .forAllEntries());
     }
 
+    @FlakyTest(bugId = 140855415)
+    @Ignore("Waiting bug feedback")
     @Test
     public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
         checkResults(result -> assertThat(result)
@@ -82,26 +74,6 @@
                 .forAllEntries());
     }
 
-    @FlakyTest(bugId = 141235985)
-    @Ignore("Waiting bug feedback")
-    @Test
-    public void checkCoveredRegion_noUncoveredRegions() {
-        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
-                getDisplayBounds()).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_navBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_statusBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(STATUS_BAR_WINDOW_TITLE).forAllEntries());
-    }
-
     @Test
     public void checkVisibility_wallpaperLayerBecomesInvisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
index 9f5cfce..91d4a05 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/OpenImeWindowTest.java
@@ -17,31 +17,39 @@
 package com.android.server.wm.flicker;
 
 import static com.android.server.wm.flicker.CommonTransitions.editTextSetFocus;
-import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test IME window opening transitions.
  * To run this test: {@code atest FlickerTests:OpenImeWindowTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-public class OpenImeWindowTest extends FlickerTestBase {
+public class OpenImeWindowTest extends NonRotationTestBase {
 
     private static final String IME_WINDOW_TITLE = "InputMethod";
 
+    public OpenImeWindowTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
+        mTestApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+    }
+
     @Before
     public void runTransition() {
-        super.runTransition(editTextSetFocus(mUiDevice)
+        run(editTextSetFocus((ImeAppHelper) mTestApp, mUiDevice, mBeginRotation)
                 .includeJankyRuns().build());
     }
 
@@ -62,10 +70,4 @@
                 .showsLayer(IME_WINDOW_TITLE)
                 .forAllEntries());
     }
-
-    @Test
-    public void checkCoveredRegion_noUncoveredRegions() {
-        checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
-                getDisplayBounds()).forAllEntries());
-    }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
index 1031baf..29b6240 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ResizeSplitScreenTest.java
@@ -24,13 +24,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.graphics.Rect;
-import android.platform.helpers.IAppHelper;
 import android.util.Rational;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.wm.flicker.helpers.ImeAppHelper;
 
 import org.junit.Before;
 import org.junit.FixMethodOrder;
@@ -38,57 +38,48 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
 
 /**
  * Test split screen resizing window transitions.
  * To run this test: {@code atest FlickerTests:ResizeSplitScreenTest}
  */
 @LargeTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 140856143)
+@FlakyTest(bugId = 140854698)
 @Ignore("Waiting bug feedback")
-public class ResizeSplitScreenTest extends FlickerTestBase {
+public class ResizeSplitScreenTest extends NonRotationTestBase {
 
-    public ResizeSplitScreenTest() {
+    private static String sSimpleActivity = "SimpleActivity";
+    private static String sImeActivity = "ImeActivity";
+
+    public ResizeSplitScreenTest(String beginRotationName, int beginRotation) {
+        super(beginRotationName, beginRotation);
+
         this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
                 "com.android.server.wm.flicker.testapp", "SimpleApp");
     }
 
     @Before
     public void runTransition() {
-        IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
-                .getInstrumentation(),
-                "com.android.server.wm.flicker.testapp", "ImeApp");
-        super.runTransition(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, new Rational(1, 3),
-                new Rational(2, 3)).includeJankyRuns().build());
-    }
-
-    @Test
-    public void checkVisibility_navBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(NAVIGATION_BAR_WINDOW_TITLE)
-                .forAllEntries());
-    }
-
-    @Test
-    public void checkVisibility_statusBarLayerIsAlwaysVisible() {
-        checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer(STATUS_BAR_WINDOW_TITLE)
-                .forAllEntries());
+        ImeAppHelper bottomApp = new ImeAppHelper(InstrumentationRegistry.getInstrumentation());
+        run(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, mBeginRotation,
+                new Rational(1, 3), new Rational(2, 3))
+                .includeJankyRuns().build());
     }
 
     @Test
     public void checkVisibility_topAppLayerIsAlwaysVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer("SimpleActivity")
+                .showsLayer(sSimpleActivity)
                 .forAllEntries());
     }
 
     @Test
     public void checkVisibility_bottomAppLayerIsAlwaysVisible() {
         checkResults(result -> LayersTraceSubject.assertThat(result)
-                .showsLayer("ImeActivity")
+                .showsLayer(sImeActivity)
                 .forAllEntries());
     }
 
@@ -149,11 +140,11 @@
                     displayBounds.bottom - getNavigationBarHeight());
 
             LayersTraceSubject.assertThat(result)
-                    .hasVisibleRegion("SimpleActivity", startingTopAppBounds)
+                    .hasVisibleRegion(sSimpleActivity, startingTopAppBounds)
                     .atTheEnd();
 
             LayersTraceSubject.assertThat(result)
-                    .hasVisibleRegion("ImeActivity", startingBottomAppBounds)
+                    .hasVisibleRegion(sImeActivity, startingBottomAppBounds)
                     .atTheEnd();
         });
     }
@@ -175,14 +166,14 @@
     @Test
     public void checkVisibility_topAppWindowIsAlwaysVisible() {
         checkResults(result -> WmTraceSubject.assertThat(result)
-                .showsAppWindow("SimpleActivity")
+                .showsAppWindow(sSimpleActivity)
                 .forAllEntries());
     }
 
     @Test
     public void checkVisibility_bottomAppWindowIsAlwaysVisible() {
         checkResults(result -> WmTraceSubject.assertThat(result)
-                .showsAppWindow("ImeActivity")
+                .showsAppWindow(sImeActivity)
                 .forAllEntries());
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java
new file mode 100644
index 0000000..42977f5
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerAppHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers;
+
+import android.app.Instrumentation;
+
+import com.android.server.wm.flicker.StandardAppHelper;
+
+public abstract class FlickerAppHelper extends StandardAppHelper {
+
+    static int sFindTimeout = 10000;
+    static String sFlickerPackage = "com.android.server.wm.flicker.testapp";
+
+    public FlickerAppHelper(Instrumentation instr, String launcherName) {
+        super(instr, sFlickerPackage, launcherName);
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java
new file mode 100644
index 0000000..56e1118
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.UiDevice;
+
+public class ImeAppAutoFocusHelper extends ImeAppHelper {
+
+    public ImeAppAutoFocusHelper(Instrumentation instr) {
+        super(instr, "ImeAppAutoFocus");
+    }
+
+    public void clickEditTextWidget(UiDevice device) {
+        // do nothing (the app is focused automatically)
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.java
new file mode 100644
index 0000000..098fd6d
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.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.server.wm.flicker.helpers;
+
+import static android.os.SystemClock.sleep;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+
+public class ImeAppHelper extends FlickerAppHelper {
+
+    ImeAppHelper(Instrumentation instr, String launcherName) {
+        super(instr, launcherName);
+    }
+
+    public ImeAppHelper(Instrumentation instr) {
+        this(instr, "ImeApp");
+    }
+
+    public void clickEditTextWidget(UiDevice device) {
+        UiObject2 editText = device.findObject(By.res(getPackage(), "plain_text_input"));
+        editText.click();
+        sleep(500);
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java
new file mode 100644
index 0000000..d00e11b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.helpers;
+
+import static com.android.server.wm.flicker.helpers.AutomationUtils.getPipWindowSelector;
+
+import android.app.Instrumentation;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+
+public class PipAppHelper extends FlickerAppHelper {
+
+    public PipAppHelper(Instrumentation instr) {
+        super(instr, "PipApp");
+    }
+
+    public void clickEnterPipButton(UiDevice device) {
+        UiObject2 enterPipButton = device.findObject(By.res(getPackage(), "enter_pip"));
+        enterPipButton.click();
+        UiObject2 pipWindow = device.wait(Until.findObject(getPipWindowSelector()), sFindTimeout);
+
+        if (pipWindow == null) {
+            throw new RuntimeException("Unable to find PIP window");
+        }
+    }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index b694172..0fe9682 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -38,6 +38,15 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".ImeActivityAutoFocus"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
+                  android:windowSoftInputMode="stateVisible"
+                  android:label="ImeAppAutoFocus">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
         <activity android:name=".PipActivity"
                   android:resizeableActivity="true"
                   android:supportsPictureInPicture="true"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index d5eb023..4708cfd 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -18,6 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:focusableInTouchMode="true"
     android:background="@android:color/holo_green_light">
     <EditText android:id="@+id/plain_text_input"
               android:layout_height="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
new file mode 100644
index 0000000..05da717
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.widget.EditText;
+
+public class ImeActivityAutoFocus extends ImeActivity {
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        EditText editTextField = findViewById(R.id.plain_text_input);
+        editTextField.requestFocus();
+    }
+}
diff --git a/tests/net/java/android/net/netlink/InetDiagSocketTest.java b/tests/net/java/android/net/netlink/InetDiagSocketTest.java
index 2adbb06..b4f6e99 100644
--- a/tests/net/java/android/net/netlink/InetDiagSocketTest.java
+++ b/tests/net/java/android/net/netlink/InetDiagSocketTest.java
@@ -18,7 +18,6 @@
 
 import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
 import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.AF_INET6;
 import static android.system.OsConstants.IPPROTO_TCP;
@@ -28,6 +27,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -45,7 +45,6 @@
 import libcore.util.HexEncoding;
 
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -152,9 +151,13 @@
 
     private void checkConnectionOwnerUid(int protocol, InetSocketAddress local,
                                          InetSocketAddress remote, boolean expectSuccess) {
-        final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID;
         final int uid = mCm.getConnectionOwnerUid(protocol, local, remote);
-        assertEquals(expectedUid, uid);
+
+        if (expectSuccess) {
+            assertEquals(Process.myUid(), uid);
+        } else {
+            assertNotEquals(Process.myUid(), uid);
+        }
     }
 
     private int findLikelyFreeUdpPort(UdpConnection conn) throws Exception {
@@ -165,11 +168,11 @@
         return localPort;
     }
 
+    /**
+     * Create a test connection for UDP and TCP sockets and verify that this
+     * {protocol, local, remote} socket result in receiving a valid UID.
+     */
     public void checkGetConnectionOwnerUid(String to, String from) throws Exception {
-        /**
-         * For TCP connections, create a test connection and verify that this
-         * {protocol, local, remote} socket result in receiving a valid UID.
-         */
         TcpConnection tcp = new TcpConnection(to, from);
         checkConnectionOwnerUid(tcp.protocol, tcp.local, tcp.remote, true);
         checkConnectionOwnerUid(IPPROTO_UDP, tcp.local, tcp.remote, false);
@@ -177,20 +180,14 @@
         checkConnectionOwnerUid(tcp.protocol, tcp.local, new InetSocketAddress(0), false);
         tcp.close();
 
-        /**
-         * For UDP connections, either a complete match {protocol, local, remote} or a
-         * partial match {protocol, local} should return a valid UID.
-         */
         UdpConnection udp = new UdpConnection(to,from);
         checkConnectionOwnerUid(udp.protocol, udp.local, udp.remote, true);
-        checkConnectionOwnerUid(udp.protocol, udp.local, new InetSocketAddress(0), true);
         checkConnectionOwnerUid(IPPROTO_TCP, udp.local, udp.remote, false);
         checkConnectionOwnerUid(udp.protocol, new InetSocketAddress(findLikelyFreeUdpPort(udp)),
                 udp.remote, false);
         udp.close();
     }
 
-    @Ignore
     @Test
     public void testGetConnectionOwnerUid() throws Exception {
         checkGetConnectionOwnerUid("::", null);
@@ -203,6 +200,16 @@
         checkGetConnectionOwnerUid("::1", "::1");
     }
 
+    /* Verify fix for b/141603906 */
+    @Test
+    public void testB141603906() throws Exception {
+        final InetSocketAddress src = new InetSocketAddress(0);
+        final InetSocketAddress dst = new InetSocketAddress(0);
+        for (int i = 1; i <= 100000; i++) {
+            mCm.getConnectionOwnerUid(IPPROTO_TCP, src, dst);
+        }
+    }
+
     // Hexadecimal representation of InetDiagReqV2 request.
     private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX =
             // struct nlmsghdr
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index cd2bd26..7029218 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -561,11 +561,17 @@
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{SYSTEM_UID1});
     }
 
-    private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+    private PackageInfo setPackagePermissions(String packageName, int uid, String[] permissions)
             throws Exception {
         PackageInfo packageInfo = packageInfoWithPermissions(permissions, PARTITION_SYSTEM);
         when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
         when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
+        return packageInfo;
+    }
+
+    private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+            throws Exception {
+        PackageInfo packageInfo = setPackagePermissions(packageName, uid, permissions);
         mObserver.onPackageAdded(packageName, uid);
         return packageInfo;
     }
@@ -616,14 +622,13 @@
     }
 
     @Test
-    public void testPackageUpdate() throws Exception {
+    public void testPackageRemoveThenAdd() throws Exception {
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
         addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
                 | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
 
-        // Remove and install the same package to simulate the update action
         when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
         mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
         mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
@@ -633,6 +638,20 @@
     }
 
     @Test
+    public void testPackageUpdate() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_NONE, new int[]{MOCK_UID1});
+
+        // When updating a package, the broadcast receiver gets two broadcasts (a remove and then an
+        // add), but the observer sees only one callback (an update).
+        setPackagePermissions(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
+        mObserver.onPackageChanged(MOCK_PACKAGE1, MOCK_UID1);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+    }
+
+    @Test
     public void testPackageUninstallWithMultiplePackages() throws Exception {
         final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
 
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 7966ba2..8a43bb4 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -169,17 +169,12 @@
   aapt::text::Printer printer(&fout);
 
   aapt::StdErrDiagnostics diagnostics;
-  auto main_command = new aapt::MainCommand(&printer, &diagnostics);
+  aapt::MainCommand main_command(&printer, &diagnostics);
 
   // Add the daemon subcommand here so it cannot be called while executing the daemon
-  main_command->AddOptionalSubcommand(
+  main_command.AddOptionalSubcommand(
       aapt::util::make_unique<aapt::DaemonCommand>(&fout, &diagnostics));
-  return main_command->Execute(args, &std::cerr);
-}
-
-// TODO(b/141312058) stop leaks
-extern "C" const char *__asan_default_options() {
-    return "detect_leaks=0";
+  return main_command.Execute(args, &std::cerr);
 }
 
 int main(int argc, char** argv) {
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index fa2b41a..ad5bb9e 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -132,11 +132,11 @@
         // $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
         //
         // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
         //
         // To regenerate run:
         // $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
-        //
-        // CHECKSTYLE:OFF Generated code
+        /
         """
 
         if (FeatureFlag.CONST_DEFS()) generateConstDefs()
diff --git a/tools/protologtool/README.md b/tools/protologtool/README.md
index 3439357..ba63957 100644
--- a/tools/protologtool/README.md
+++ b/tools/protologtool/README.md
@@ -8,8 +8,13 @@
 
 ### Code transformation
 
-Command: `process <protolog class path> <protolog implementation class path>
- <protolog groups class path> <config.jar> [<input.java>] <output.srcjar>`
+Command: `protologtool transform-protolog-calls 
+    --protolog-class <protolog class name> 
+    --protolog-impl-class <protolog implementation class name>
+    --loggroups-class <protolog groups class name>
+    --loggroups-jar <config jar path>
+    --output-srcjar <output.srcjar>
+    [<input.java>]`
 
 In this mode ProtoLogTool transforms every ProtoLog logging call in form of:
 ```java
@@ -17,16 +22,20 @@
 ```
 into:
 ```java
-if (GROUP_NAME.isLogToAny()) {
-    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, "Format string %d %s or null", value1, value2);
+if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
+    int protoLogParam0 = value1;
+    String protoLogParam1 = String.valueOf(value2);
+    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 123456, 0b0100, "Format string %d %s or null", protoLogParam0, protoLogParam1);
 }
 ```
 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
+ string, log level and log group name and inserted after the `ProtoLogGroup` argument. After the hash
+ we insert a bitmask specifying the types of logged parameters. 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.
+ returns false the log statement is removed entirely from the resultant code. The real generated code is inlined
+ and a number of new line characters is added as to preserve line numbering in file.
 
 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
@@ -34,8 +43,12 @@
 
 ### Viewer config generation
 
-Command: `viewerconf <protolog class path> <protolog implementation class path
-<protolog groups class path> <config.jar> [<input.java>] <output.json>`
+Command: `generate-viewer-config
+    --protolog-class <protolog class name> 
+    --loggroups-class <protolog groups class name>
+    --loggroups-jar <config jar path>
+    --viewer-conf <viewer.json>
+    [<input.java>]`
 
 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:
@@ -46,8 +59,9 @@
     "123456": {
       "message": "Format string %d %s",
       "level": "ERROR",
-      "group": "GROUP_NAME"
-    },
+      "group": "GROUP_NAME",
+      "at": "com\/android\/server\/example\/Class.java"
+    }
   },
   "groups": {
     "GROUP_NAME": {
@@ -60,13 +74,13 @@
 
 ### Binary log viewing
 
-Command: `read <viewer.json> <wm_log.pb>`
+Command: `read-log --viewer-conf <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
+ProtoLog is a generic 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,
@@ -94,8 +108,7 @@
 
 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.
+After doing any changes to logging groups or statements you should build the project and follow instructions printed by the tool.
 
 ## How to change settings on device in runtime?
 Use the `adb shell su root cmd window logging` command. To get help just type
diff --git a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
index 2e48d97..a52c804 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt
@@ -32,9 +32,14 @@
                 .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 checkWildcardStaticImported(code: CompilationUnit, className: String, fileName: String) {
+        code.findAll(ImportDeclaration::class.java)
+                .forEach { im ->
+                    if (im.isStatic && im.isAsterisk && im.name.toString() == className) {
+                        throw IllegalImportException("Wildcard static imports of $className " +
+                                "methods are not supported.", ParsingContext(fileName, im))
+                    }
+                }
     }
 
     fun isClassImportedOrSamePackage(code: CompilationUnit, className: String): Boolean {
@@ -58,24 +63,19 @@
                 .map { im -> im.name.toString().substringAfterLast('.') }.toSet()
     }
 
-    fun concatMultilineString(expr: Expression): String {
+    fun concatMultilineString(expr: Expression, context: ParsingContext): String {
         return when (expr) {
             is StringLiteralExpr -> expr.asString()
             is BinaryExpr -> when {
                 expr.operator == BinaryExpr.Operator.PLUS ->
-                    concatMultilineString(expr.left) + concatMultilineString(expr.right)
+                    concatMultilineString(expr.left, context) +
+                            concatMultilineString(expr.right, context)
                 else -> throw InvalidProtoLogCallException(
-                        "messageString must be a string literal " +
-                                "or concatenation of string literals.", expr)
+                        "expected a string literal " +
+                                "or concatenation of string literals, got: $expr", context)
             }
-            else -> throw InvalidProtoLogCallException("messageString must be a string literal " +
-                    "or concatenation of string literals.", expr)
-        }
-    }
-
-    fun getPositionString(fileName: String): String {
-        return when {
-            else -> fileName
+            else -> throw InvalidProtoLogCallException("expected a string literal " +
+                    "or concatenation of string literals, got: $expr", context)
         }
     }
 }
diff --git a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
index 7759f35..e88f0f8 100644
--- a/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt
@@ -22,7 +22,7 @@
     DEBUG, VERBOSE, INFO, WARN, ERROR, WTF;
 
     companion object {
-        fun getLevelForMethodName(name: String, node: Node): LogLevel {
+        fun getLevelForMethodName(name: String, node: Node, context: ParsingContext): LogLevel {
             return when (name) {
                 "d" -> DEBUG
                 "v" -> VERBOSE
@@ -30,7 +30,8 @@
                 "w" -> WARN
                 "e" -> ERROR
                 "wtf" -> WTF
-                else -> throw InvalidProtoLogCallException("Unknown log level $name", node)
+                else ->
+                    throw InvalidProtoLogCallException("Unknown log level $name in $node", context)
             }
         }
     }
diff --git a/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
new file mode 100644
index 0000000..c6aedfc
--- /dev/null
+++ b/tools/protologtool/src/com/android/protolog/tool/ParsingContext.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.protolog.tool
+
+import com.github.javaparser.ast.Node
+
+data class ParsingContext(val filePath: String, val lineNumber: Int) {
+    constructor(filePath: String, node: Node)
+            : this(filePath, if (node.range.isPresent) node.range.get().begin.line else -1)
+
+    constructor() : this("", -1)
+}
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
index eae6396..2181cf6 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt
@@ -38,23 +38,27 @@
     private fun getLogGroupName(
         expr: Expression,
         isClassImported: Boolean,
-        staticImports: Set<String>
+        staticImports: Set<String>,
+        fileName: String
     ): String {
+        val context = ParsingContext(fileName, expr)
         return when (expr) {
             is NameExpr -> when {
                 expr.nameAsString in staticImports -> expr.nameAsString
                 else ->
-                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+                            context)
             }
             is FieldAccessExpr -> when {
                 expr.scope.toString() == protoLogGroupClassName
                         || isClassImported &&
                         expr.scope.toString() == protoLogGroupSimpleClassName -> expr.nameAsString
                 else ->
-                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup", expr)
+                    throw InvalidProtoLogCallException("Unknown/not imported ProtoLogGroup: $expr",
+                            context)
             }
             else -> throw InvalidProtoLogCallException("Invalid group argument " +
-                    "- must be ProtoLogGroup enum member reference", expr)
+                    "- must be ProtoLogGroup enum member reference: $expr", context)
         }
     }
 
@@ -69,12 +73,10 @@
                 !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.")
-        }
+    open fun process(code: CompilationUnit, callVisitor: ProtoLogCallVisitor?, fileName: String):
+            CompilationUnit {
+        CodeUtils.checkWildcardStaticImported(code, protoLogClassName, fileName)
+        CodeUtils.checkWildcardStaticImported(code, protoLogGroupClassName, fileName)
 
         val isLogClassImported = CodeUtils.isClassImportedOrSamePackage(code, protoLogClassName)
         val staticLogImports = CodeUtils.staticallyImportedMethods(code, protoLogClassName)
@@ -86,22 +88,25 @@
                 .filter { call ->
                     isProtoCall(call, isLogClassImported, staticLogImports)
                 }.forEach { call ->
+                    val context = ParsingContext(fileName, call)
                     if (call.arguments.size < 2) {
                         throw InvalidProtoLogCallException("Method signature does not match " +
-                                "any ProtoLog method.", call)
+                                "any ProtoLog method: $call", context)
                     }
 
-                    val messageString = CodeUtils.concatMultilineString(call.getArgument(1))
+                    val messageString = CodeUtils.concatMultilineString(call.getArgument(1),
+                            context)
                     val groupNameArg = call.getArgument(0)
                     val groupName =
-                            getLogGroupName(groupNameArg, isGroupClassImported, staticGroupImports)
+                            getLogGroupName(groupNameArg, isGroupClassImported,
+                                    staticGroupImports, fileName)
                     if (groupName !in groupMap) {
                         throw InvalidProtoLogCallException("Unknown group argument " +
-                                "- not a ProtoLogGroup enum member", call)
+                                "- not a ProtoLogGroup enum member: $call", context)
                     }
 
                     callVisitor?.processCall(call, messageString, LogLevel.getLevelForMethodName(
-                            call.name.toString(), call), groupMap.getValue(groupName))
+                            call.name.toString(), call, context), groupMap.getValue(groupName))
                 }
         return code
     }
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 53834a6..97f3de2 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -17,7 +17,9 @@
 package com.android.protolog.tool
 
 import com.android.protolog.tool.CommandOptions.Companion.USAGE
+import com.github.javaparser.ParseProblemException
 import com.github.javaparser.StaticJavaParser
+import com.github.javaparser.ast.CompilationUnit
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileOutputStream
@@ -48,7 +50,7 @@
         command.javaSourceArgs.forEach { path ->
             val file = File(path)
             val text = file.readText()
-            val code = StaticJavaParser.parse(text)
+            val code = tryParse(text, path)
             val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
                     .get().nameAsString else ""
             val newPath = pack.replace('.', '/') + '/' + file.name
@@ -66,6 +68,19 @@
         out.close()
     }
 
+    private fun tryParse(code: String, fileName: String): CompilationUnit {
+        try {
+            return StaticJavaParser.parse(code)
+        } catch (ex: ParseProblemException) {
+            val problem = ex.problems.first()
+            throw ParsingException("Java parsing erro" +
+                    "r: ${problem.verboseMessage}",
+                    ParsingContext(fileName, problem.location.orElse(null)
+                            ?.begin?.range?.orElse(null)?.begin?.line
+                            ?: 0))
+        }
+    }
+
     private fun viewerConf(command: CommandOptions) {
         val groups = ProtoLogGroupReader()
                 .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg)
@@ -76,7 +91,7 @@
             val file = File(path)
             val text = file.readText()
             if (containsProtoLogText(text, command.protoLogClassNameArg)) {
-                val code = StaticJavaParser.parse(text)
+                val code = tryParse(text, path)
                 val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration
                         .get().nameAsString else ""
                 val newPath = pack.replace('.', '/') + '/' + file.name
@@ -104,8 +119,11 @@
                 CommandOptions.READ_LOG_CMD -> read(command)
             }
         } catch (ex: InvalidCommandException) {
-            println(ex.message)
+            println("\n${ex.message}\n")
             showHelpAndExit()
+        } catch (ex: CodeProcessingException) {
+            println("\n${ex.message}\n")
+            exitProcess(1)
         }
     }
 }
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index c2964a3..00fd038 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -73,8 +73,7 @@
         }
         val ifStmt: IfStmt
         if (group.enabled) {
-            val position = CodeUtils.getPositionString(fileName)
-            val hash = CodeUtils.hash(position, messageString, level, group)
+            val hash = CodeUtils.hash(fileName, messageString, level, group)
             val newCall = call.clone()
             if (!group.textEnabled) {
                 // Remove message string if text logging is not enabled by default.
@@ -99,7 +98,8 @@
                 NodeList<Expression>(newCall.arguments[0].clone()))
             if (argTypes.size != call.arguments.size - 2) {
                 throw InvalidProtoLogCallException(
-                        "Number of arguments does not mach format string", call)
+                        "Number of arguments (${argTypes.size} does not mach format" +
+                                " string in: $call", ParsingContext(fileName, call))
             }
             val blockStmt = BlockStmt()
             if (argTypes.isNotEmpty()) {
@@ -225,7 +225,7 @@
         processedCode = code.split('\n').toMutableList()
         offsets = IntArray(processedCode.size)
         LexicalPreservingPrinter.setup(compilationUnit)
-        protoLogCallProcessor.process(compilationUnit, this)
+        protoLogCallProcessor.process(compilationUnit, this, fileName)
         // return LexicalPreservingPrinter.print(compilationUnit)
         return processedCode.joinToString("\n")
     }
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
index 172de0e..941455a 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt
@@ -32,13 +32,14 @@
         group: LogGroup
     ) {
         if (group.enabled) {
-            val position = CodeUtils.getPositionString(fileName)
+            val position = fileName
             val key = CodeUtils.hash(position, messageString, level, group)
             if (statements.containsKey(key)) {
                 if (statements[key] != LogCall(messageString, level, group, position)) {
                     throw HashCollisionException(
                             "Please modify the log message \"$messageString\" " +
-                                    "or \"${statements[key]}\" - their hashes are equal.")
+                                    "or \"${statements[key]}\" - their hashes are equal.",
+                            ParsingContext(fileName, call))
                 }
             } else {
                 groups.add(group)
@@ -54,7 +55,7 @@
 
     fun processClass(unit: CompilationUnit, fileName: String) {
         this.fileName = fileName
-        protoLogCallVisitor.process(unit, this)
+        protoLogCallVisitor.process(unit, this, fileName)
     }
 
     fun build(): String {
diff --git a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
index 0401d8f..ae00df1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
@@ -16,16 +16,23 @@
 
 package com.android.protolog.tool
 
-import com.github.javaparser.ast.Node
 import java.lang.Exception
-import java.lang.RuntimeException
 
-class HashCollisionException(message: String) : RuntimeException(message)
+open class CodeProcessingException(message: String, context: ParsingContext)
+    : Exception("Code processing error in ${context.filePath}:${context.lineNumber}:\n" +
+        "  $message")
 
-class IllegalImportException(message: String) : Exception(message)
+class HashCollisionException(message: String, context: ParsingContext) :
+        CodeProcessingException(message, context)
 
-class InvalidProtoLogCallException(message: String, node: Node)
-    : RuntimeException("$message\nAt: $node")
+class IllegalImportException(message: String, context: ParsingContext) :
+        CodeProcessingException("Illegal import: $message", context)
+
+class InvalidProtoLogCallException(message: String, context: ParsingContext)
+    : CodeProcessingException("InvalidProtoLogCall: $message", context)
+
+class ParsingException(message: String, context: ParsingContext)
+    : CodeProcessingException(message, context)
 
 class InvalidViewerConfigException(message: String) : Exception(message)
 
diff --git a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
index 0acbc90..b916f8f 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt
@@ -55,40 +55,40 @@
                 LogLevel.DEBUG, LogGroup("test2", true, true, "TAG")))
     }
 
-    @Test
-    fun isWildcardStaticImported_true() {
+    @Test(expected = IllegalImportException::class)
+    fun checkWildcardStaticImported_true() {
         val code = """package org.example.test;
             import static org.example.Test.*;
         """
-        assertTrue(CodeUtils.isWildcardStaticImported(
-                StaticJavaParser.parse(code), "org.example.Test"))
+        CodeUtils.checkWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test", "")
     }
 
     @Test
-    fun isWildcardStaticImported_notStatic() {
+    fun checkWildcardStaticImported_notStatic() {
         val code = """package org.example.test;
             import org.example.Test.*;
         """
-        assertFalse(CodeUtils.isWildcardStaticImported(
-                StaticJavaParser.parse(code), "org.example.Test"))
+        CodeUtils.checkWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test", "")
     }
 
     @Test
-    fun isWildcardStaticImported_differentClass() {
+    fun checkWildcardStaticImported_differentClass() {
         val code = """package org.example.test;
             import static org.example.Test2.*;
         """
-        assertFalse(CodeUtils.isWildcardStaticImported(
-                StaticJavaParser.parse(code), "org.example.Test"))
+        CodeUtils.checkWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test", "")
     }
 
     @Test
-    fun isWildcardStaticImported_notWildcard() {
+    fun checkWildcardStaticImported_notWildcard() {
         val code = """package org.example.test;
             import org.example.Test.test;
         """
-        assertFalse(CodeUtils.isWildcardStaticImported(
-                StaticJavaParser.parse(code), "org.example.Test"))
+        CodeUtils.checkWildcardStaticImported(
+                StaticJavaParser.parse(code), "org.example.Test", "")
     }
 
     @Test
@@ -156,7 +156,7 @@
     @Test
     fun concatMultilineString_single() {
         val str = StringLiteralExpr("test")
-        val out = CodeUtils.concatMultilineString(str)
+        val out = CodeUtils.concatMultilineString(str, ParsingContext())
         assertEquals("test", out)
     }
 
@@ -166,7 +166,7 @@
             "test" + "abc"
         """
         val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
-        val out = CodeUtils.concatMultilineString(code)
+        val out = CodeUtils.concatMultilineString(code, ParsingContext())
         assertEquals("testabc", out)
     }
 
@@ -176,7 +176,7 @@
             "test" + "abc" + "1234" + "test"
         """
         val code = StaticJavaParser.parseExpression<BinaryExpr>(str)
-        val out = CodeUtils.concatMultilineString(code)
+        val out = CodeUtils.concatMultilineString(code, ParsingContext())
         assertEquals("testabc1234test", out)
     }
 }
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
index d20ce7e..97f67a0 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt
@@ -66,7 +66,7 @@
         """
         groupMap["TEST"] = LogGroup("TEST", true, false, "WindowManager")
         groupMap["ERROR"] = LogGroup("ERROR", true, true, "WindowManagerERROR")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
         assertEquals(2, calls.size)
         var c = calls[0]
         assertEquals("test %b", c.messageString)
@@ -93,7 +93,7 @@
             }
         """
         groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
         checkCalls()
     }
 
@@ -112,7 +112,7 @@
             }
         """
         groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
         checkCalls()
     }
 
@@ -130,7 +130,7 @@
             }
         """
         groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
     }
 
     @Test
@@ -147,7 +147,7 @@
             }
         """
         groupMap["TEST"] = LogGroup("TEST", true, true, "WindowManager")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
         assertEquals(0, calls.size)
     }
 
@@ -162,7 +162,7 @@
                 }
             }
         """
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
     }
 
     @Test(expected = InvalidProtoLogCallException::class)
@@ -176,7 +176,7 @@
                 }
             }
         """
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
     }
 
     @Test(expected = InvalidProtoLogCallException::class)
@@ -190,7 +190,7 @@
                 }
             }
         """
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
     }
 
     @Test(expected = InvalidProtoLogCallException::class)
@@ -204,7 +204,7 @@
                 }
             }
         """
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
     }
 
     @Test
@@ -220,7 +220,7 @@
             }
         """
         groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
-        visitor.process(StaticJavaParser.parse(code), processor)
+        visitor.process(StaticJavaParser.parse(code), processor, "")
         checkCalls()
     }
 }
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 18504b6..e746300 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -175,7 +175,8 @@
         var code = StaticJavaParser.parse(TEST_CODE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -212,7 +213,8 @@
         var code = StaticJavaParser.parse(TEST_CODE_MULTICALLS)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             val calls = code.findAll(MethodCallExpr::class.java)
@@ -254,7 +256,8 @@
         var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -292,7 +295,8 @@
         var code = StaticJavaParser.parse(TEST_CODE_NO_PARAMS)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test",
@@ -326,7 +330,8 @@
         var code = StaticJavaParser.parse(TEST_CODE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -363,7 +368,8 @@
         var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
@@ -402,7 +408,8 @@
         var code = StaticJavaParser.parse(TEST_CODE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0], "test %d %f",
@@ -426,7 +433,8 @@
         var code = StaticJavaParser.parse(TEST_CODE_MULTILINE)
 
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(code.findAll(MethodCallExpr::class.java)[0],
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
index d3f8c76..2b6abcd 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt
@@ -50,7 +50,8 @@
     @Test
     fun processClass() {
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
@@ -78,7 +79,8 @@
     @Test
     fun processClass_nonUnique() {
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,
@@ -102,7 +104,8 @@
     @Test
     fun processClass_disabled() {
         Mockito.`when`(processor.process(any(CompilationUnit::class.java),
-                any(ProtoLogCallVisitor::class.java))).thenAnswer { invocation ->
+                any(ProtoLogCallVisitor::class.java), any(String::class.java)))
+                .thenAnswer { invocation ->
             val visitor = invocation.arguments[1] as ProtoLogCallVisitor
 
             visitor.processCall(MethodCallExpr(), TEST1.messageString, LogLevel.INFO,