Merge "Import RotateSecondaryKeyTask"
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..fcf69a7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7858,6 +7858,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/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/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/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/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/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/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/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/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/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/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/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/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 720074b..47d911d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2752,7 +2752,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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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,