Merge "Add SkiaProfileRenderer to draw visual debugging info"
diff --git a/cmds/settings/Android.mk b/cmds/settings/Android.mk
index 05deb99..8a8d1bb 100644
--- a/cmds/settings/Android.mk
+++ b/cmds/settings/Android.mk
@@ -3,11 +3,6 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := settings
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_JAVA_LIBRARY)
-
 include $(CLEAR_VARS)
 LOCAL_MODULE := settings
 LOCAL_SRC_FILES := settings
diff --git a/cmds/settings/settings b/cmds/settings/settings
index ef459ca..d41ccc62 100755
--- a/cmds/settings/settings
+++ b/cmds/settings/settings
@@ -1,5 +1,2 @@
-# Script to start "settings" on the device
-#
-base=/system
-export CLASSPATH=$base/framework/settings.jar
-exec app_process $base/bin com.android.commands.settings.SettingsCmd "$@"
+#!/system/bin/sh
+cmd settings "$@"
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
deleted file mode 100644
index e63a1f5..0000000
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ /dev/null
@@ -1,320 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.commands.settings;
-
-import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
-import android.app.IActivityManager.ContentProviderHolder;
-import android.content.IContentProvider;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public final class SettingsCmd {
-
-    enum CommandVerb {
-        UNSPECIFIED,
-        GET,
-        PUT,
-        DELETE,
-        LIST,
-    }
-
-    static String[] mArgs;
-    int mNextArg;
-    int mUser = -1;     // unspecified
-    CommandVerb mVerb = CommandVerb.UNSPECIFIED;
-    String mTable = null;
-    String mKey = null;
-    String mValue = null;
-
-    public static void main(String[] args) {
-        if (args == null || args.length < 2) {
-            printUsage();
-            return;
-        }
-
-        mArgs = args;
-        try {
-            new SettingsCmd().run();
-        } catch (Exception e) {
-            System.err.println("Unable to run settings command");
-        }
-    }
-
-    public void run() {
-        boolean valid = false;
-        String arg;
-        try {
-            while ((arg = nextArg()) != null) {
-                if ("--user".equals(arg)) {
-                    if (mUser != -1) {
-                        // --user specified more than once; invalid
-                        break;
-                    }
-                    arg = nextArg();
-                    if ("current".equals(arg) || "cur".equals(arg)) {
-                        mUser = UserHandle.USER_CURRENT;
-                    } else {
-                        mUser = Integer.parseInt(arg);
-                    }
-                } else if (mVerb == CommandVerb.UNSPECIFIED) {
-                    if ("get".equalsIgnoreCase(arg)) {
-                        mVerb = CommandVerb.GET;
-                    } else if ("put".equalsIgnoreCase(arg)) {
-                        mVerb = CommandVerb.PUT;
-                    } else if ("delete".equalsIgnoreCase(arg)) {
-                        mVerb = CommandVerb.DELETE;
-                    } else if ("list".equalsIgnoreCase(arg)) {
-                        mVerb = CommandVerb.LIST;
-                    } else {
-                        // invalid
-                        System.err.println("Invalid command: " + arg);
-                        break;
-                    }
-                } else if (mTable == null) {
-                    if (!"system".equalsIgnoreCase(arg)
-                            && !"secure".equalsIgnoreCase(arg)
-                            && !"global".equalsIgnoreCase(arg)) {
-                        System.err.println("Invalid namespace '" + arg + "'");
-                        break;  // invalid
-                    }
-                    mTable = arg.toLowerCase();
-                    if (mVerb == CommandVerb.LIST) {
-                        valid = true;
-                        break;
-                    }
-                } else if (mVerb == CommandVerb.GET || mVerb == CommandVerb.DELETE) {
-                    mKey = arg;
-                    if (mNextArg >= mArgs.length) {
-                        valid = true;
-                    } else {
-                        System.err.println("Too many arguments");
-                    }
-                    break;
-                } else if (mKey == null) {
-                    mKey = arg;
-                    // keep going; there's another PUT arg
-                } else {    // PUT, final arg
-                    mValue = arg;
-                    if (mNextArg >= mArgs.length) {
-                        valid = true;
-                    } else {
-                        System.err.println("Too many arguments");
-                    }
-                    break;
-                }
-            }
-        } catch (Exception e) {
-            valid = false;
-        }
-
-        if (valid) {
-            try {
-                IActivityManager activityManager = ActivityManagerNative.getDefault();
-                if (mUser == UserHandle.USER_CURRENT) {
-                    mUser = activityManager.getCurrentUser().id;
-                }
-                if (mUser < 0) {
-                    mUser = UserHandle.USER_SYSTEM;
-                }
-                IContentProvider provider = null;
-                IBinder token = new Binder();
-                try {
-                    ContentProviderHolder holder = activityManager.getContentProviderExternal(
-                            "settings", UserHandle.USER_SYSTEM, token);
-                    if (holder == null) {
-                        throw new IllegalStateException("Could not find settings provider");
-                    }
-                    provider = holder.provider;
-
-                    switch (mVerb) {
-                        case GET:
-                            System.out.println(getForUser(provider, mUser, mTable, mKey));
-                            break;
-                        case PUT:
-                            putForUser(provider, mUser, mTable, mKey, mValue);
-                            break;
-                        case DELETE:
-                            System.out.println("Deleted "
-                                    + deleteForUser(provider, mUser, mTable, mKey) + " rows");
-                            break;
-                        case LIST:
-                            for (String line : listForUser(provider, mUser, mTable)) {
-                                System.out.println(line);
-                            }
-                            break;
-                        default:
-                            System.err.println("Unspecified command");
-                            break;
-                    }
-
-                } finally {
-                    if (provider != null) {
-                        activityManager.removeContentProviderExternal("settings", token);
-                    }
-                }
-            } catch (Exception e) {
-                System.err.println("Error while accessing settings provider");
-                e.printStackTrace();
-            }
-
-        } else {
-            printUsage();
-        }
-    }
-
-    private List<String> listForUser(IContentProvider provider, int userHandle, String table) {
-        final Uri uri = "system".equals(table) ? Settings.System.CONTENT_URI
-                : "secure".equals(table) ? Settings.Secure.CONTENT_URI
-                : "global".equals(table) ? Settings.Global.CONTENT_URI
-                : null;
-        final ArrayList<String> lines = new ArrayList<String>();
-        if (uri == null) {
-            return lines;
-        }
-        try {
-            final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null,
-                    null, null);
-            try {
-                while (cursor != null && cursor.moveToNext()) {
-                    lines.add(cursor.getString(1) + "=" + cursor.getString(2));
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-            Collections.sort(lines);
-        } catch (RemoteException e) {
-            System.err.println("List failed in " + table + " for user " + userHandle);
-        }
-        return lines;
-    }
-
-    private String nextArg() {
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        mNextArg++;
-        return arg;
-    }
-
-    String getForUser(IContentProvider provider, int userHandle,
-            final String table, final String key) {
-        final String callGetCommand;
-        if ("system".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SYSTEM;
-        else if ("secure".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_SECURE;
-        else if ("global".equals(table)) callGetCommand = Settings.CALL_METHOD_GET_GLOBAL;
-        else {
-            System.err.println("Invalid table; no put performed");
-            throw new IllegalArgumentException("Invalid table " + table);
-        }
-
-        String result = null;
-        try {
-            Bundle arg = new Bundle();
-            arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-            Bundle b = provider.call(resolveCallingPackage(), callGetCommand, key, arg);
-            if (b != null) {
-                result = b.getPairValue();
-            }
-        } catch (RemoteException e) {
-            System.err.println("Can't read key " + key + " in " + table + " for user " + userHandle);
-        }
-        return result;
-    }
-
-    void putForUser(IContentProvider provider, int userHandle,
-            final String table, final String key, final String value) {
-        final String callPutCommand;
-        if ("system".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SYSTEM;
-        else if ("secure".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_SECURE;
-        else if ("global".equals(table)) callPutCommand = Settings.CALL_METHOD_PUT_GLOBAL;
-        else {
-            System.err.println("Invalid table; no put performed");
-            return;
-        }
-
-        try {
-            Bundle arg = new Bundle();
-            arg.putString(Settings.NameValueTable.VALUE, value);
-            arg.putInt(Settings.CALL_METHOD_USER_KEY, userHandle);
-            provider.call(resolveCallingPackage(), callPutCommand, key, arg);
-        } catch (RemoteException e) {
-            System.err.println("Can't set key " + key + " in " + table + " for user " + userHandle);
-        }
-    }
-
-    int deleteForUser(IContentProvider provider, int userHandle,
-            final String table, final String key) {
-        Uri targetUri;
-        if ("system".equals(table)) targetUri = Settings.System.getUriFor(key);
-        else if ("secure".equals(table)) targetUri = Settings.Secure.getUriFor(key);
-        else if ("global".equals(table)) targetUri = Settings.Global.getUriFor(key);
-        else {
-            System.err.println("Invalid table; no delete performed");
-            throw new IllegalArgumentException("Invalid table " + table);
-        }
-
-        int num = 0;
-        try {
-            num = provider.delete(resolveCallingPackage(), targetUri, null, null);
-        } catch (RemoteException e) {
-            System.err.println("Can't clear key " + key + " in " + table + " for user "
-                    + userHandle);
-        }
-        return num;
-    }
-
-    private static void printUsage() {
-        System.err.println("usage:  settings [--user <USER_ID> | current] get namespace key");
-        System.err.println("        settings [--user <USER_ID> | current] put namespace key value");
-        System.err.println("        settings [--user <USER_ID> | current] delete namespace key");
-        System.err.println("        settings [--user <USER_ID> | current] list namespace");
-        System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
-        System.err.println("If '--user <USER_ID> | current' is not given, the operations are "
-                + "performed on the system user.");
-    }
-
-    public static String resolveCallingPackage() {
-        switch (android.os.Process.myUid()) {
-            case Process.ROOT_UID: {
-                return "root";
-            }
-
-            case Process.SHELL_UID: {
-                return "com.android.shell";
-            }
-
-            default: {
-                return null;
-            }
-        }
-    }
-}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 15f6361..141c899 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,6 +752,15 @@
             return true;
         }
 
+        case MOVE_STACK_TO_DISPLAY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int stackId = data.readInt();
+            int displayId = data.readInt();
+            moveStackToDisplay(stackId, displayId);
+            reply.writeNoException();
+            return true;
+        }
+
         case MOVE_TASK_TO_FRONT_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int task = data.readInt();
@@ -3885,6 +3894,19 @@
         reply.recycle();
         return list;
     }
+    public void moveStackToDisplay(int stackId, int displayId) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(stackId);
+        data.writeInt(displayId);
+        mRemote.transact(MOVE_STACK_TO_DISPLAY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    @Override
     public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException
     {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index c075ed6..cada1b8 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -448,13 +448,6 @@
             mGuard.open("release");
         }
 
-        void attachToDisplay(int displayId) {
-            try {
-                mIActivityContainer.attachToDisplay(displayId);
-            } catch (RemoteException e) {
-            }
-        }
-
         void setSurface(Surface surface, int width, int height, int density)
                 throws RemoteException {
             mIActivityContainer.setSurface(surface, width, height, density);
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index 170aff3..1ff3c87 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -25,7 +25,7 @@
 
 /** @hide */
 interface IActivityContainer {
-    void attachToDisplay(int displayId);
+    void addToDisplay(int displayId);
     void setSurface(in Surface surface, int width, int height, int density);
     int startActivity(in Intent intent);
     int startActivityIntentSender(in IIntentSender intentSender);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 0323651..7166789 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -143,6 +143,7 @@
     public List<RunningServiceInfo> getServices(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
             throws RemoteException;
+    public void moveStackToDisplay(int stackId, int displayId) throws RemoteException;
     public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
     public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
     public void moveTaskBackwards(int task) throws RemoteException;
@@ -1100,4 +1101,5 @@
     int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
     int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
     int UNREGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+402;
+    int MOVE_STACK_TO_DISPLAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
 }
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
index b7715da..d07b407 100644
--- a/core/java/android/hardware/radio/RadioMetadata.java
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -530,14 +530,13 @@
         Bitmap bmp = null;
         try {
             bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
-        } catch (Exception e) {
-        } finally {
-            if (bmp == null) {
-                return -1;
+            if (bmp != null) {
+                mBundle.putParcelable(key, bmp);
+                return 0;
             }
-            mBundle.putParcelable(key, bmp);
-            return 0;
+        } catch (Exception e) {
         }
+        return -1;
     }
 
     int putClockFromNative(int nativeKey, long utcEpochSeconds, int timezoneOffsetInMinutes) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 89c6eb2..1b905a0 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5161,18 +5161,10 @@
                 "accessibility_display_magnification_scale";
 
         /**
-         * Setting that specifies whether the display magnification should be
-         * automatically updated. If this fearture is enabled the system will
-         * exit magnification mode or pan the viewport when a context change
-         * occurs. For example, on staring a new activity or rotating the screen,
-         * the system may zoom out so the user can see the new context he is in.
-         * Another example is on showing a window that is not visible in the
-         * magnified viewport the system may pan the viewport to make the window
-         * the has popped up so the user knows that the context has changed.
-         * Whether a screen magnification is performed is controlled by
-         * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+         * Unused mangnification setting
          *
          * @hide
+         * @deprecated
          */
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
                 "accessibility_display_magnification_auto_update";
@@ -6491,7 +6483,6 @@
             ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
             ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
             ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-            ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
             ACCESSIBILITY_SCRIPT_INJECTION,
             ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
             ENABLED_ACCESSIBILITY_SERVICES,
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index a8d70c4..8a9bb33 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -339,13 +339,6 @@
         boolean isVisibleLw();
 
         /**
-         * Like {@link #isVisibleLw}, but also counts a window that is currently
-         * "hidden" behind the keyguard as visible.  This allows us to apply
-         * things like window flags that impact the keyguard.
-         */
-        boolean isVisibleOrBehindKeyguardLw();
-
-        /**
          * Is this window currently visible to the user on-screen?  It is
          * displayed either if it is visible or it is currently running an
          * animation before no longer being visible.  Must be called with the
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 71e77c4..2829744 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -35,16 +35,16 @@
  */
 interface IAccessibilityManager {
 
-    int addClient(IAccessibilityManagerClient client, int userId);
+    oneway void interrupt(int userId);
 
-    void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+    oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
+
+    int addClient(IAccessibilityManagerClient client, int userId);
 
     List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
 
     List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
 
-    void interrupt(int userId);
-
     int addAccessibilityInteractionConnection(IWindow windowToken,
         in IAccessibilityInteractionConnection connection, int userId);
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index b971856..accbabd 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -49,8 +49,9 @@
 }
 
 void RenderNodeDrawable::onDraw(SkCanvas* canvas) {
-    //negative and positive Z order are drawn out of order
-    if (MathUtils::isZero(mRenderNode->properties().getZ())) {
+    //negative and positive Z order are drawn out of order, if this render node drawable is in
+    //a reordering section
+    if ((!mInReorderingSection) || MathUtils::isZero(mRenderNode->properties().getZ())) {
         this->forceDraw(canvas);
     }
 }
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index 0762f37..a2ffc6c 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -56,10 +56,12 @@
      *      we should draw into the contents of the layer or compose the existing contents of the
      *      layer into the canvas.
      */
-    explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true)
+    explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true,
+            bool inReorderingSection = false)
             : mRenderNode(node)
             , mRecordedTransform(canvas->getTotalMatrix())
-            , mComposeLayer(composeLayer) {}
+            , mComposeLayer(composeLayer)
+            , mInReorderingSection(inReorderingSection) {}
 
     /**
      * Draws into the canvas this render node and its children. If the node is marked as a
@@ -138,6 +140,11 @@
     std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
 
     /*
+     * True if the render node is in a reordering section
+     */
+    bool mInReorderingSection;
+
+    /*
      *  Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
      */
     void drawContent(SkCanvas* canvas) const;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 8a42983..ecc6d51 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -107,7 +107,7 @@
     }
 
     // record the child node
-    mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas());
+    mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
     drawDrawable(&mDisplayList->mChildNodes.back());
 
     // use staging property, since recording on UI thread
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index c68ca4e..623d971 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -51,85 +51,56 @@
     ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
 }
 
-TEST(RenderNodeDrawable, drawContent) {
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
-    SkCanvas& canvas = *surface->getCanvas();
-    canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-
-    //create a RenderNodeDrawable backed by a RenderNode backed by a SkLiteRecorder
-    auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [](RenderProperties& props, SkiaRecordingCanvas& recorder) {
-            recorder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
-        });
-    RenderNodeDrawable drawable(rootNode.get(), &canvas, false);
-
-    //negative and positive Z order are drawn out of order
-    rootNode->animatorProperties().setElevation(10.0f);
-    canvas.drawDrawable(&drawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-    rootNode->animatorProperties().setElevation(-10.0f);
-    canvas.drawDrawable(&drawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
-
-    //zero Z are drawn immediately
-    rootNode->animatorProperties().setElevation(0.0f);
-    canvas.drawDrawable(&drawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
+    SkPaint paint;
+    // order put in blue channel, transparent so overlapped content doesn't get rejected
+    paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
+    canvas->drawRect(0, 0, 100, 100, paint);
 }
 
-//TODO: another test that verifies equal z values are drawn in order, and barriers prevent Z
-//intermixing (model after FrameBuilder zReorder)
-TEST(RenderNodeDrawable, drawAndReorder) {
-    //this test exercises StartReorderBarrierDrawable, EndReorderBarrierDrawable and
-    //SkiaRecordingCanvas
-    auto surface = SkSurface::MakeRasterN32Premul(4, 4);
-    SkCanvas& canvas = *surface->getCanvas();
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) {
+    auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [expectedDrawOrder, z](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+        props.setTranslationZ(z);
+    });
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
 
-    canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+TEST(RenderNodeDrawable, zReorder) {
+    class ZReorderCanvas : public SkCanvas {
+    public:
+        ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
+            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+        }
+        int getIndex() { return mIndex; }
+    protected:
+        int mIndex = 0;
+    };
 
-    //-z draws to all 4 pixels (RED)
-    auto redNode = TestUtils::createSkiaNode(0, 0, 4, 4,
-        [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
-            redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
-            props.setElevation(-10.0f);
-        }, "redNode");
+    auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
+        drawOrderedRect(&canvas, 1);
+        canvas.insertReorderBarrier(true);
+        drawOrderedNode(&canvas, 6, 2.0f);
+        drawOrderedRect(&canvas, 3);
+        drawOrderedNode(&canvas, 4, 0.0f);
+        drawOrderedRect(&canvas, 5);
+        drawOrderedNode(&canvas, 2, -2.0f);
+        drawOrderedNode(&canvas, 7, 2.0f);
+        canvas.insertReorderBarrier(false);
+        drawOrderedRect(&canvas, 8);
+        drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+    });
 
-    //0z draws to bottom 2 pixels (GREEN)
-    auto bottomHalfGreenNode = TestUtils::createSkiaNode(0, 0, 4, 4,
-            [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
-                SkPaint greenPaint;
-                greenPaint.setColor(SK_ColorGREEN);
-                greenPaint.setStyle(SkPaint::kFill_Style);
-                bottomHalfGreenCanvas.drawRect(0, 2, 4, 4, greenPaint);
-                props.setElevation(0.0f);
-            }, "bottomHalfGreenNode");
-
-    //+z draws to right 2 pixels (BLUE)
-    auto rightHalfBlueNode = TestUtils::createSkiaNode(0, 0, 4, 4,
-        [](RenderProperties& props, SkiaRecordingCanvas& rightHalfBlueCanvas) {
-            SkPaint bluePaint;
-            bluePaint.setColor(SK_ColorBLUE);
-            bluePaint.setStyle(SkPaint::kFill_Style);
-            rightHalfBlueCanvas.drawRect(2, 0, 4, 4, bluePaint);
-            props.setElevation(10.0f);
-        }, "rightHalfBlueNode");
-
-    auto rootNode = TestUtils::createSkiaNode(0, 0, 4, 4,
-            [&](RenderProperties& props, SkiaRecordingCanvas& rootRecorder) {
-                rootRecorder.insertReorderBarrier(true);
-                //draw in reverse Z order, so Z alters draw order
-                rootRecorder.drawRenderNode(rightHalfBlueNode.get());
-                rootRecorder.drawRenderNode(bottomHalfGreenNode.get());
-                rootRecorder.drawRenderNode(redNode.get());
-            }, "rootNode");
-
-    RenderNodeDrawable drawable3(rootNode.get(), &canvas, false);
-    canvas.drawDrawable(&drawable3);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 3), SK_ColorGREEN);
-    ASSERT_EQ(TestUtils::getColor(surface, 3, 3), SK_ColorBLUE);
+    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ZReorderCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(parent.get(), &canvas, false);
+    canvas.drawDrawable(&drawable);
+    EXPECT_EQ(10, canvas.getIndex());
 }
 
 TEST(RenderNodeDrawable, composeOnLayer)
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index bd68fec..64576ec 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -236,18 +236,16 @@
          */
         public static final int VOICE_CALL = 4;
 
-        /** Microphone audio source with same orientation as camera if available, the main
-         *  device microphone otherwise */
+        /** Microphone audio source tuned for video recording, with the same orientation
+         *  as the camera if available. */
         public static final int CAMCORDER = 5;
 
-        /** Microphone audio source tuned for voice recognition if available, behaves like
-         *  {@link #DEFAULT} otherwise. */
+        /** Microphone audio source tuned for voice recognition. */
         public static final int VOICE_RECOGNITION = 6;
 
         /** Microphone audio source tuned for voice communications such as VoIP. It
          *  will for instance take advantage of echo cancellation or automatic gain control
-         *  if available. It otherwise behaves like {@link #DEFAULT} if no voice processing
-         *  is applied.
+         *  if available.
          */
         public static final int VOICE_COMMUNICATION = 7;
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index d55bb4f..dd543a3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1310,10 +1310,6 @@
                 loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                         R.fraction.def_accessibility_display_magnification_scale, 1);
                 stmt.close();
-                stmt = db.compileStatement("INSERT INTO secure(name,value) VALUES(?,?);");
-                loadBooleanSetting(stmt,
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
-                        R.bool.def_accessibility_display_magnification_auto_update);
 
                 db.setTransactionSuccessful();
             } finally {
@@ -2508,10 +2504,6 @@
             loadFractionSetting(stmt, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
                     R.fraction.def_accessibility_display_magnification_scale, 1);
 
-            loadBooleanSetting(stmt,
-                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
-                    R.bool.def_accessibility_display_magnification_auto_update);
-
             loadBooleanSetting(stmt, Settings.Secure.USER_SETUP_COMPLETE,
                     R.bool.def_user_setup_complete);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index f1d1b1f..88ee33c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2175,7 +2175,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 134;
+            private static final int SETTINGS_VERSION = 135;
 
             private final int mUserId;
 
@@ -2545,6 +2545,14 @@
                     currentVersion = 134;
                 }
 
+                if (currentVersion == 134) {
+                    // Remove setting that specifies if magnification values should be preserved.
+                    // This setting defaulted to true and never has a UI.
+                    getSecureSettingsLocked(userId).deleteSettingLocked(
+                            Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE);
+                    currentVersion = 135;
+                }
+
                 if (currentVersion != newVersion) {
                     Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
                             + newVersion + " left it at "
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b141454..01ffe01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -105,9 +105,7 @@
         // be invoked when we're done so that the caller can drop the pulse wakelock.
         mPulseCallback = callback;
         mPulseReason = reason;
-        if (mDozeParameters.getAlwaysOn()) {
-            mHandler.post(mPulseIn);
-        }
+        mHandler.post(mPulseIn);
     }
 
     /**
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index ba0725e..48bc27e 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -689,6 +689,7 @@
     ACTION_WIFI_ADD_NETWORK = 134;
 
     // ACTION: Settings > Wi-Fi > [Long press network] > Connect to network
+    //   SUBTYPE: true if connecting to a saved network, false if not
     // CATEGORY: SETTINGS
     // OS: 6.0
     ACTION_WIFI_CONNECT = 135;
@@ -704,6 +705,7 @@
     ACTION_WIFI_FORGET = 137;
 
     // ACTION: Settings > Wi-Fi > Toggle off
+    //   SUBTYPE: true if connected to network before toggle, false if not
     // CATEGORY: SETTINGS
     // OS: 6.0
     ACTION_WIFI_OFF = 138;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c89f158..3ea03f5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -829,9 +829,10 @@
      * @param centerX the new screen-relative center X coordinate
      * @param centerY the new screen-relative center Y coordinate
      */
-    void notifyMagnificationChanged(@NonNull Region region,
+    public void notifyMagnificationChanged(@NonNull Region region,
             float scale, float centerX, float centerY) {
         synchronized (mLock) {
+            notifyClearAccessibilityCacheLocked();
             notifyMagnificationChangedLocked(region, scale, centerX, centerY);
         }
     }
@@ -901,10 +902,6 @@
         mSecurityPolicy.onTouchInteractionEnd();
     }
 
-    void onMagnificationStateChanged() {
-        notifyClearAccessibilityCacheLocked();
-    }
-
     private void switchUser(int userId) {
         synchronized (mLock) {
             if (mCurrentUserId == userId && mInitialized) {
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 7886b9e..f65046c 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -21,8 +21,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
 
-import android.animation.ObjectAnimator;
-import android.animation.TypeEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
@@ -34,12 +32,10 @@
 import android.graphics.Region;
 import android.os.AsyncTask;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.MathUtils;
-import android.util.Property;
 import android.util.Slog;
 import android.view.MagnificationSpec;
 import android.view.View;
@@ -53,27 +49,29 @@
  * from the accessibility manager and related classes. It is responsible for
  * holding the current state of magnification and animation, and it handles
  * communication between the accessibility manager and window manager.
+ *
+ * Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the
+ * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
+ * constraints.
  */
-class MagnificationController {
+class MagnificationController implements Handler.Callback {
     private static final String LOG_TAG = "MagnificationController";
 
-    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
+    public static final float MIN_SCALE = 1.0f;
+    public static final float MAX_SCALE = 5.0f;
 
-    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
+    private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
 
     private static final int INVALID_ID = -1;
 
     private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
 
-    private static final float MIN_SCALE = 1.0f;
-    private static final float MAX_SCALE = 5.0f;
-
-    /**
-     * The minimum scaling factor that can be persisted to secure settings.
-     * This must be > 1.0 to ensure that magnification is actually set to an
-     * enabled state when the scaling factor is restored from settings.
-     */
-    private static final float MIN_PERSISTED_SCALE = 2.0f;
+    // Messages
+    private static final int MSG_SEND_SPEC_TO_ANIMATION = 1;
+    private static final int MSG_SCREEN_TURNED_OFF = 2;
+    private static final int MSG_ON_MAGNIFIED_BOUNDS_CHANGED = 3;
+    private static final int MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED = 4;
+    private static final int MSG_ON_USER_CONTEXT_CHANGED = 5;
 
     private final Object mLock;
 
@@ -90,46 +88,95 @@
     private final Rect mTempRect1 = new Rect();
 
     private final AccessibilityManagerService mAms;
-    private final ContentResolver mContentResolver;
+
+    private final SettingsBridge mSettingsBridge;
 
     private final ScreenStateObserver mScreenStateObserver;
-    private final WindowStateObserver mWindowStateObserver;
 
     private final SpecAnimationBridge mSpecAnimationBridge;
 
+    private final WindowManagerInternal.MagnificationCallbacks mWMCallbacks =
+            new WindowManagerInternal.MagnificationCallbacks () {
+                @Override
+                public void onMagnificationRegionChanged(Region region) {
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.arg1 = Region.obtain(region);
+                    mHandler.obtainMessage(MSG_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
+                }
+
+                @Override
+                public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
+                    final SomeArgs args = SomeArgs.obtain();
+                    args.argi1 = left;
+                    args.argi2 = top;
+                    args.argi3 = right;
+                    args.argi4 = bottom;
+                    mHandler.obtainMessage(MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED, args)
+                            .sendToTarget();
+                }
+
+                @Override
+                public void onRotationChanged(int rotation) {
+                    // Treat as context change and reset
+                    mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
+                }
+
+                @Override
+                public void onUserContextChanged() {
+                    mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED);
+                }
+            };
+
     private int mUserId;
 
+    private final long mMainThreadId;
+
+    private Handler mHandler;
+
     private int mIdOfLastServiceToMagnify = INVALID_ID;
 
+    private final WindowManagerInternal mWindowManager;
+
     // Flag indicating that we are registered with window manager.
     private boolean mRegistered;
 
     private boolean mUnregisterPending;
 
     public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
+        this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class),
+                new ValueAnimator(), new SettingsBridge(context.getContentResolver()));
+        mHandler = new Handler(context.getMainLooper(), this);
+    }
+
+    public MagnificationController(Context context, AccessibilityManagerService ams, Object lock,
+            Handler handler, WindowManagerInternal windowManagerInternal,
+            ValueAnimator valueAnimator, SettingsBridge settingsBridge) {
+        mHandler = handler;
+        mWindowManager = windowManagerInternal;
+        mMainThreadId = context.getMainLooper().getThread().getId();
         mAms = ams;
-        mContentResolver = context.getContentResolver();
         mScreenStateObserver = new ScreenStateObserver(context, this);
-        mWindowStateObserver = new WindowStateObserver(context, this);
         mLock = lock;
-        mSpecAnimationBridge = new SpecAnimationBridge(context, mLock);
+        mSpecAnimationBridge = new SpecAnimationBridge(
+                context, mLock, mWindowManager, valueAnimator);
+        mSettingsBridge = settingsBridge;
     }
 
     /**
      * Start tracking the magnification region for services that control magnification and the
      * magnification gesture handler.
      *
-     * This tracking imposes a cost on the system, so we avoid tracking this data
-     * unless it's required.
+     * This tracking imposes a cost on the system, so we avoid tracking this data unless it's
+     * required.
      */
     public void register() {
         synchronized (mLock) {
             if (!mRegistered) {
                 mScreenStateObserver.register();
-                mWindowStateObserver.register();
+                mWindowManager.setMagnificationCallbacks(mWMCallbacks);
                 mSpecAnimationBridge.setEnabled(true);
                 // Obtain initial state.
-                mWindowStateObserver.getMagnificationRegion(mMagnificationRegion);
+                mWindowManager.getMagnificationRegion(mMagnificationRegion);
                 mMagnificationRegion.getBounds(mMagnificationBounds);
                 mRegistered = true;
             }
@@ -164,7 +211,7 @@
         if (mRegistered) {
             mSpecAnimationBridge.setEnabled(false);
             mScreenStateObserver.unregister();
-            mWindowStateObserver.unregister();
+            mWindowManager.setMagnificationCallbacks(null);
             mMagnificationRegion.setEmpty();
             mRegistered = false;
         }
@@ -183,40 +230,22 @@
      * Update our copy of the current magnification region
      *
      * @param magnified the magnified region
-     * @param updateSpec {@code true} to update the scale and center based on
-     *                   the region bounds, {@code false} to leave them as-is
      */
-    private void onMagnificationRegionChanged(Region magnified, boolean updateSpec) {
+    private void onMagnificationRegionChanged(Region magnified) {
         synchronized (mLock) {
             if (!mRegistered) {
                 // Don't update if we've unregistered
                 return;
             }
-            boolean magnificationChanged = false;
-            boolean boundsChanged = false;
-
             if (!mMagnificationRegion.equals(magnified)) {
                 mMagnificationRegion.set(magnified);
                 mMagnificationRegion.getBounds(mMagnificationBounds);
-                boundsChanged = true;
-            }
-            if (updateSpec) {
-                final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
-                final float scale = sentSpec.scale;
-                final float offsetX = sentSpec.offsetX;
-                final float offsetY = sentSpec.offsetY;
-
-                // Compute the new center and update spec as needed.
-                final float centerX = (mMagnificationBounds.width() / 2.0f
-                        + mMagnificationBounds.left - offsetX) / scale;
-                final float centerY = (mMagnificationBounds.height() / 2.0f
-                        + mMagnificationBounds.top - offsetY) / scale;
-                magnificationChanged = setScaleAndCenterLocked(
-                        scale, centerX, centerY, false, INVALID_ID);
-            }
-
-            // If magnification changed we already notified for the change.
-            if (boundsChanged && updateSpec && !magnificationChanged) {
+                // It's possible that our magnification spec is invalid with the new bounds.
+                // Adjust the current spec's offsets if necessary.
+                if (updateCurrentSpecWithOffsetsLocked(
+                        mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) {
+                    sendSpecToAnimation(mCurrentMagnificationSpec, false);
+                }
                 onMagnificationChangedLocked();
             }
         }
@@ -328,7 +357,7 @@
      *
      * @return the scale currently used by the window manager
      */
-    public float getSentScale() {
+    private float getSentScale() {
         return mSpecAnimationBridge.mSentMagnificationSpec.scale;
     }
 
@@ -339,7 +368,7 @@
      *
      * @return the X offset currently used by the window manager
      */
-    public float getSentOffsetX() {
+    private float getSentOffsetX() {
         return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
     }
 
@@ -350,7 +379,7 @@
      *
      * @return the Y offset currently used by the window manager
      */
-    public float getSentOffsetY() {
+    private float getSentOffsetY() {
         return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
     }
 
@@ -380,7 +409,7 @@
             onMagnificationChangedLocked();
         }
         mIdOfLastServiceToMagnify = INVALID_ID;
-        mSpecAnimationBridge.updateSentSpec(spec, animate);
+        sendSpecToAnimation(spec, animate);
         return changed;
     }
 
@@ -475,7 +504,7 @@
     private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
             boolean animate, int id) {
         final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
-        mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
+        sendSpecToAnimation(mCurrentMagnificationSpec, animate);
         if (isMagnifying() && (id != INVALID_ID)) {
             mIdOfLastServiceToMagnify = id;
         }
@@ -483,27 +512,28 @@
     }
 
     /**
-     * Offsets the center of the magnified region.
+     * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the
+     * opposite direction as the offsets passed in here.
      *
-     * @param offsetX the amount in pixels to offset the X center
-     * @param offsetY the amount in pixels to offset the Y center
+     * @param offsetX the amount in pixels to offset the region in the X direction, in current
+     * screen pixels.
+     * @param offsetY the amount in pixels to offset the region in the Y direction, in current
+     * screen pixels.
      * @param id the ID of the service requesting the change
      */
-    public void offsetMagnifiedRegionCenter(float offsetX, float offsetY, int id) {
+    public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) {
         synchronized (mLock) {
             if (!mRegistered) {
                 return;
             }
 
-            final MagnificationSpec currSpec = mCurrentMagnificationSpec;
-            final float nonNormOffsetX = currSpec.offsetX - offsetX;
-            currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
-            final float nonNormOffsetY = currSpec.offsetY - offsetY;
-            currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+            final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
+            final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
+            updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
             if (id != INVALID_ID) {
                 mIdOfLastServiceToMagnify = id;
             }
-            mSpecAnimationBridge.updateSentSpec(currSpec, false);
+            sendSpecToAnimation(mCurrentMagnificationSpec, false);
         }
     }
 
@@ -517,7 +547,6 @@
     }
 
     private void onMagnificationChangedLocked() {
-        mAms.onMagnificationStateChanged();
         mAms.notifyMagnificationChanged(mMagnificationRegion,
                 getScale(), getCenterX(), getCenterY());
         if (mUnregisterPending && !isMagnifying()) {
@@ -535,8 +564,7 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
-                Settings.Secure.putFloatForUser(mContentResolver,
-                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
+                mSettingsBridge.putMagnificationScale(scale, userId);
                 return null;
             }
         }.execute();
@@ -550,9 +578,7 @@
      *         scale if none is available
      */
     public float getPersistedScale() {
-        return Settings.Secure.getFloatForUser(mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
-                DEFAULT_MAGNIFICATION_SCALE, mUserId);
+        return mSettingsBridge.getMagnificationScale(mUserId);
     }
 
     /**
@@ -578,36 +604,20 @@
             scale = getScale();
         }
 
-        // Ensure requested center is within the magnification region.
-        if (!magnificationRegionContains(centerX, centerY)) {
-            return false;
-        }
-
         // Compute changes.
-        final MagnificationSpec currSpec = mCurrentMagnificationSpec;
         boolean changed = false;
 
         final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
-        if (Float.compare(currSpec.scale, normScale) != 0) {
-            currSpec.scale = normScale;
+        if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) {
+            mCurrentMagnificationSpec.scale = normScale;
             changed = true;
         }
 
         final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
-                + mMagnificationBounds.left - centerX * scale;
-        final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
-        if (Float.compare(currSpec.offsetX, offsetX) != 0) {
-            currSpec.offsetX = offsetX;
-            changed = true;
-        }
-
+                + mMagnificationBounds.left - centerX * normScale;
         final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
-                + mMagnificationBounds.top - centerY * scale;
-        final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
-        if (Float.compare(currSpec.offsetY, offsetY) != 0) {
-            currSpec.offsetY = offsetY;
-            changed = true;
-        }
+                + mMagnificationBounds.top - centerY * normScale;
+        changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
 
         if (changed) {
             onMagnificationChangedLocked();
@@ -616,6 +626,21 @@
         return changed;
     }
 
+    private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
+        boolean changed = false;
+        final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
+        if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
+            mCurrentMagnificationSpec.offsetX = offsetX;
+            changed = true;
+        }
+        final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
+        if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) {
+            mCurrentMagnificationSpec.offsetY = offsetY;
+            changed = true;
+        }
+        return changed;
+    }
+
     private float getMinOffsetXLocked() {
         final float viewportWidth = mMagnificationBounds.width();
         return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
@@ -643,12 +668,6 @@
         }
     }
 
-    private boolean isScreenMagnificationAutoUpdateEnabled() {
-        return (Settings.Secure.getInt(mContentResolver,
-                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
-                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
-    }
-
     /**
      * Resets magnification if magnification and auto-update are both enabled.
      *
@@ -658,7 +677,7 @@
      */
     boolean resetIfNeeded(boolean animate) {
         synchronized (mLock) {
-            if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
+            if (isMagnifying()) {
                 reset(animate);
                 return true;
             }
@@ -715,18 +734,61 @@
             }
 
             final float scale = getScale();
-            offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale, INVALID_ID);
+            offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID);
         }
     }
 
+    private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+        if (Thread.currentThread().getId() == mMainThreadId) {
+            mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
+        } else {
+            mHandler.obtainMessage(MSG_SEND_SPEC_TO_ANIMATION,
+                    animate ? 1 : 0, 0, spec).sendToTarget();
+        }
+    }
+
+    private void onScreenTurnedOff() {
+        mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF);
+    }
+
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_SEND_SPEC_TO_ANIMATION:
+                final boolean animate = msg.arg1 == 1;
+                final MagnificationSpec spec = (MagnificationSpec) msg.obj;
+                mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
+                break;
+            case MSG_SCREEN_TURNED_OFF:
+                resetIfNeeded(false);
+                break;
+            case MSG_ON_MAGNIFIED_BOUNDS_CHANGED: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final Region magnifiedBounds = (Region) args.arg1;
+                onMagnificationRegionChanged(magnifiedBounds);
+                magnifiedBounds.recycle();
+                args.recycle();
+            } break;
+            case MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
+                final SomeArgs args = (SomeArgs) msg.obj;
+                final int left = args.argi1;
+                final int top = args.argi2;
+                final int right = args.argi3;
+                final int bottom = args.argi4;
+                requestRectangleOnScreen(left, top, right, bottom);
+                args.recycle();
+            } break;
+            case MSG_ON_USER_CONTEXT_CHANGED:
+                resetIfNeeded(true);
+                break;
+        }
+        return true;
+    }
+
     /**
      * Class responsible for animating spec on the main thread and sending spec
      * updates to the window manager.
      */
-    private static class SpecAnimationBridge {
-        private static final int ACTION_UPDATE_SPEC = 1;
-
-        private final Handler mHandler;
+    private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener {
         private final WindowManagerInternal mWindowManager;
 
         /**
@@ -735,34 +797,33 @@
          */
         private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
 
-        /**
-         * The animator that updates the sent spec. This should only be accessed
-         * and modified on the main (e.g. animation) thread.
-         */
-        private final ValueAnimator mTransformationAnimator;
+        private final MagnificationSpec mStartMagnificationSpec = MagnificationSpec.obtain();
 
-        private final long mMainThreadId;
+        private final MagnificationSpec mEndMagnificationSpec = MagnificationSpec.obtain();
+
+        private final MagnificationSpec mTmpMagnificationSpec = MagnificationSpec.obtain();
+
+        /**
+         * The animator should only be accessed and modified on the main (e.g. animation) thread.
+         */
+        private final ValueAnimator mValueAnimator;
+
         private final Object mLock;
 
         @GuardedBy("mLock")
         private boolean mEnabled = false;
 
-        private SpecAnimationBridge(Context context, Object lock) {
+        private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm,
+                ValueAnimator animator) {
             mLock = lock;
-            final Looper mainLooper = context.getMainLooper();
-            mMainThreadId = mainLooper.getThread().getId();
-
-            mHandler = new UpdateHandler(context);
-            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-
-            final MagnificationSpecProperty property = new MagnificationSpecProperty();
-            final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
+            mWindowManager = wm;
             final long animationDuration = context.getResources().getInteger(
                     R.integer.config_longAnimTime);
-            mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
-                    mSentMagnificationSpec);
-            mTransformationAnimator.setDuration(animationDuration);
-            mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+            mValueAnimator = animator;
+            mValueAnimator.setDuration(animationDuration);
+            mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
+            mValueAnimator.setFloatValues(0.0f, 1.0f);
+            mValueAnimator.addUpdateListener(this);
         }
 
         /**
@@ -781,22 +842,9 @@
             }
         }
 
-        public void updateSentSpec(MagnificationSpec spec, boolean animate) {
-            if (Thread.currentThread().getId() == mMainThreadId) {
-                // Already on the main thread, don't bother proxying.
-                updateSentSpecInternal(spec, animate);
-            } else {
-                mHandler.obtainMessage(ACTION_UPDATE_SPEC,
-                        animate ? 1 : 0, 0, spec).sendToTarget();
-            }
-        }
-
-        /**
-         * Updates the sent spec.
-         */
-        private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
-            if (mTransformationAnimator.isRunning()) {
-                mTransformationAnimator.cancel();
+        public void updateSentSpecMainThread(MagnificationSpec spec, boolean animate) {
+            if (mValueAnimator.isRunning()) {
+                mValueAnimator.cancel();
             }
 
             // If the current and sent specs don't match, update the sent spec.
@@ -812,11 +860,6 @@
             }
         }
 
-        private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
-            mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
-            mTransformationAnimator.start();
-        }
-
         private void setMagnificationSpecLocked(MagnificationSpec spec) {
             if (mEnabled) {
                 if (DEBUG_SET_MAGNIFICATION_SPEC) {
@@ -828,71 +871,40 @@
             }
         }
 
-        private class UpdateHandler extends Handler {
-            public UpdateHandler(Context context) {
-                super(context.getMainLooper());
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case ACTION_UPDATE_SPEC:
-                        final boolean animate = msg.arg1 == 1;
-                        final MagnificationSpec spec = (MagnificationSpec) msg.obj;
-                        updateSentSpecInternal(spec, animate);
-                        break;
-                }
-            }
+        private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
+            mEndMagnificationSpec.setTo(toSpec);
+            mStartMagnificationSpec.setTo(mSentMagnificationSpec);
+            mValueAnimator.start();
         }
 
-        private static class MagnificationSpecProperty
-                extends Property<SpecAnimationBridge, MagnificationSpec> {
-            public MagnificationSpecProperty() {
-                super(MagnificationSpec.class, "spec");
-            }
-
-            @Override
-            public MagnificationSpec get(SpecAnimationBridge object) {
-                synchronized (object.mLock) {
-                    return object.mSentMagnificationSpec;
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            synchronized (mLock) {
+                if (mEnabled) {
+                    float fract = animation.getAnimatedFraction();
+                    mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale +
+                            (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract;
+                    mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX +
+                            (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX)
+                                    * fract;
+                    mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY +
+                            (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY)
+                                    * fract;
+                    synchronized (mLock) {
+                        setMagnificationSpecLocked(mTmpMagnificationSpec);
+                    }
                 }
             }
-
-            @Override
-            public void set(SpecAnimationBridge object, MagnificationSpec value) {
-                synchronized (object.mLock) {
-                    object.setMagnificationSpecLocked(value);
-                }
-            }
-        }
-
-        private static class MagnificationSpecEvaluator
-                implements TypeEvaluator<MagnificationSpec> {
-            private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
-
-            @Override
-            public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
-                    MagnificationSpec toSpec) {
-                final MagnificationSpec result = mTempSpec;
-                result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
-                result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
-                result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
-                return result;
-            }
         }
     }
 
     private static class ScreenStateObserver extends BroadcastReceiver {
-        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
-
         private final Context mContext;
         private final MagnificationController mController;
-        private final Handler mHandler;
 
         public ScreenStateObserver(Context context, MagnificationController controller) {
             mContext = context;
             mController = controller;
-            mHandler = new StateChangeHandler(context);
         }
 
         public void register() {
@@ -905,151 +917,27 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
-                    intent.getAction()).sendToTarget();
-        }
-
-        private void handleOnScreenStateChange() {
-            mController.resetIfNeeded(false);
-        }
-
-        private class StateChangeHandler extends Handler {
-            public StateChangeHandler(Context context) {
-                super(context.getMainLooper());
-            }
-
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MESSAGE_ON_SCREEN_STATE_CHANGE:
-                        handleOnScreenStateChange();
-                        break;
-                }
-            }
+            mController.onScreenTurnedOff();
         }
     }
 
-    /**
-     * This class handles the screen magnification when accessibility is enabled.
-     */
-    private static class WindowStateObserver
-            implements WindowManagerInternal.MagnificationCallbacks {
-        private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
-        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
-        private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
-        private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
+    // Extra class to get settings so tests can mock it
+    public static class SettingsBridge {
+        private final ContentResolver mContentResolver;
 
-        private final MagnificationController mController;
-        private final WindowManagerInternal mWindowManager;
-        private final Handler mHandler;
-
-        private boolean mSpecIsDirty;
-
-        public WindowStateObserver(Context context, MagnificationController controller) {
-            mController = controller;
-            mWindowManager = LocalServices.getService(WindowManagerInternal.class);
-            mHandler = new CallbackHandler(context);
+        public SettingsBridge(ContentResolver contentResolver) {
+            mContentResolver = contentResolver;
         }
 
-        public void register() {
-            mWindowManager.setMagnificationCallbacks(this);
+        public void putMagnificationScale(float value, int userId) {
+            Settings.Secure.putFloatForUser(mContentResolver,
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId);
         }
 
-        public void unregister() {
-            mWindowManager.setMagnificationCallbacks(null);
-        }
-
-        @Override
-        public void onMagnificationRegionChanged(Region magnificationRegion) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = Region.obtain(magnificationRegion);
-            mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
-        }
-
-        private void handleOnMagnifiedBoundsChanged(Region magnificationRegion) {
-            mController.onMagnificationRegionChanged(magnificationRegion, mSpecIsDirty);
-            mSpecIsDirty = false;
-        }
-
-        @Override
-        public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.argi1 = left;
-            args.argi2 = top;
-            args.argi3 = right;
-            args.argi4 = bottom;
-            mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
-        }
-
-        private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
-            mController.requestRectangleOnScreen(left, top, right, bottom);
-        }
-
-        @Override
-        public void onRotationChanged(int rotation) {
-            mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
-        }
-
-        private void handleOnRotationChanged() {
-            // If there was a rotation and magnification is still enabled,
-            // we'll need to rewrite the spec to reflect the new screen
-            // configuration. Conveniently, we'll receive a callback from
-            // the window manager with updated bounds for the magnified
-            // region.
-            mSpecIsDirty = !mController.resetIfNeeded(true);
-        }
-
-        @Override
-        public void onUserContextChanged() {
-            mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
-        }
-
-        private void handleOnUserContextChanged() {
-            mController.resetIfNeeded(true);
-        }
-
-        /**
-         * This method is used to get the magnification region in the tiny time slice between
-         * registering the callbacks and handling the message.
-         * TODO: Elimiante this extra path, perhaps by processing the message immediately
-         *
-         * @param outMagnificationRegion
-         */
-        public void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
-            mWindowManager.getMagnificationRegion(outMagnificationRegion);
-        }
-
-        private class CallbackHandler extends Handler {
-            public CallbackHandler(Context context) {
-                super(context.getMainLooper());
-            }
-
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
-                        final SomeArgs args = (SomeArgs) message.obj;
-                        final Region magnifiedBounds = (Region) args.arg1;
-                        handleOnMagnifiedBoundsChanged(magnifiedBounds);
-                        magnifiedBounds.recycle();
-                    } break;
-                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
-                        final SomeArgs args = (SomeArgs) message.obj;
-                        final int left = args.argi1;
-                        final int top = args.argi2;
-                        final int right = args.argi3;
-                        final int bottom = args.argi4;
-                        handleOnRectangleOnScreenRequested(left, top, right, bottom);
-                        args.recycle();
-                    } break;
-                    case MESSAGE_ON_USER_CONTEXT_CHANGED: {
-                        handleOnUserContextChanged();
-                    } break;
-                    case MESSAGE_ON_ROTATION_CHANGED: {
-                        handleOnRotationChanged();
-                    } break;
-                }
-            }
+        public float getMagnificationScale(int userId) {
+            return Settings.Secure.getFloatForUser(mContentResolver,
+                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+                    DEFAULT_MAGNIFICATION_SCALE, userId);
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 39bc809..f6e5340 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -381,7 +381,7 @@
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
-            mMagnificationController.offsetMagnifiedRegionCenter(distanceX, distanceY,
+            mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
             return true;
         }
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 6e871a8..2c4cfb9 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -16,7 +16,9 @@
 package com.android.server;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import com.android.internal.content.PackageMonitor;
@@ -113,6 +115,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.Window;
 import android.view.WindowManager;
 import android.view.WindowManagerInternal;
 import android.view.inputmethod.EditorInfo;
@@ -468,6 +471,7 @@
 
     private AlertDialog.Builder mDialogBuilder;
     private AlertDialog mSwitchingDialog;
+    private IBinder mSwitchingDialogToken = new Binder();
     private View mSwitchingDialogTitleView;
     private Toast mSubtypeSwitchedByShortCutToast;
     private InputMethodInfo[] mIms;
@@ -3288,11 +3292,16 @@
 
             mSwitchingDialog = mDialogBuilder.create();
             mSwitchingDialog.setCanceledOnTouchOutside(true);
-            mSwitchingDialog.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
-            mSwitchingDialog.getWindow().getAttributes().privateFlags |=
-                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
-            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
+            final Window w = mSwitchingDialog.getWindow();
+            final WindowManager.LayoutParams attrs = w.getAttributes();
+            w.setType(TYPE_INPUT_METHOD_DIALOG);
+            // Use an alternate token for the dialog for that window manager can group the token
+            // with other IME windows based on type vs. grouping based on whichever token happens
+            // to get selected by the system later on.
+            attrs.token = mSwitchingDialogToken;
+            attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            attrs.setTitle("Select input method");
+            w.setAttributes(attrs);
             updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
             mSwitchingDialog.show();
         }
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 0cad814..a2207b2 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -585,6 +585,20 @@
                     Slog.e(TAG, "Unable to remove tied profile key", e);
                 }
             }
+
+            boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_WATCH);
+            // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+            // and device management the lockscreen must be re-enabled now for users that upgrade.
+            if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+                final int userCount = users.size();
+                for (int i = 0; i < userCount; i++) {
+                    int id = users.get(i).id;
+                    setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+                }
+                setString("migrated_wear_lockscreen_disabled", "true", 0);
+                Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
+            }
         } catch (RemoteException re) {
             Slog.e(TAG, "Unable to migrate old data", re);
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5d41d36..5aee770 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9513,6 +9513,22 @@
     }
 
     @Override
+    public void moveStackToDisplay(int stackId, int displayId) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveStackToDisplay()");
+
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG_STACK) Slog.d(TAG_STACK, "moveStackToDisplay: moving stackId=" + stackId
+                        + " to displayId=" + displayId);
+                mStackSupervisor.moveStackToDisplayLocked(stackId, displayId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public boolean removeTask(int taskId) {
         enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
         synchronized (this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8234f35..3efd300 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -215,6 +215,8 @@
                     return runGetInactive(pw);
                 case "send-trim-memory":
                     return runSendTrimMemory(pw);
+                case "display":
+                    return runDisplay(pw);
                 case "stack":
                     return runStack(pw);
                 case "task":
@@ -1631,12 +1633,23 @@
         return 0;
     }
 
+    int runDisplay(PrintWriter pw) throws RemoteException {
+        String op = getNextArgRequired();
+        switch (op) {
+            case "move-stack":
+                return runDisplayMoveStack(pw);
+            default:
+                getErrPrintWriter().println("Error: unknown command '" + op + "'");
+                return -1;
+        }
+    }
+
     int runStack(PrintWriter pw) throws RemoteException {
         String op = getNextArgRequired();
         switch (op) {
             case "start":
                 return runStackStart(pw);
-            case "movetask":
+            case "move-task":
                 return runStackMoveTask(pw);
             case "resize":
                 return runStackResize(pw);
@@ -1691,6 +1704,15 @@
         return new Rect(left, top, right, bottom);
     }
 
+    int runDisplayMoveStack(PrintWriter pw) throws RemoteException {
+        String stackIdStr = getNextArgRequired();
+        int stackId = Integer.parseInt(stackIdStr);
+        String displayIdStr = getNextArgRequired();
+        int displayId = Integer.parseInt(displayIdStr);
+        mInterface.moveStackToDisplay(stackId, displayId);
+        return 0;
+    }
+
     int runStackStart(PrintWriter pw) throws RemoteException {
         String displayIdStr = getNextArgRequired();
         int displayId = Integer.parseInt(displayIdStr);
@@ -2450,10 +2472,13 @@
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
             pw.println("      Send a memory trim event to a <PROCESS>.");
+            pw.println("  display [COMMAND] [...]: sub-commands for operating on displays.");
+            pw.println("       move-stack <STACK_ID> <DISPLAY_ID>");
+            pw.println("           Move <STACK_ID> from its current display to <DISPLAY_ID>.");
             pw.println("  stack [COMMAND] [...]: sub-commands for operating on activity stacks.");
             pw.println("       start <DISPLAY_ID> <INTENT>");
             pw.println("           Start a new activity on <DISPLAY_ID> using <INTENT>");
-            pw.println("       movetask <TASK_ID> <STACK_ID> [true|false]");
+            pw.println("       move-task <TASK_ID> <STACK_ID> [true|false]");
             pw.println("           Move <TASK_ID> from its current stack to the top (true) or");
             pw.println("           bottom (false) of <STACK_ID>.");
             pw.println("       resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1c3a8851..0d79980 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -449,10 +449,23 @@
                 ? new LaunchingTaskPositioner() : null;
     }
 
-    void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+    /** Adds the stack to specified display and calls WindowManager to do the same. */
+    void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+        final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
+                onTop);
+        postAddToDisplay(activityDisplay, bounds);
+    }
+
+    /**
+     * Updates internal state after adding to new display.
+     * @param activityDisplay New display to which this stack was attached.
+     * @param bounds Updated bounds.
+     */
+    private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
+            Rect bounds) {
         mDisplayId = activityDisplay.mDisplayId;
         mStacks = activityDisplay.mStacks;
-        mBounds = mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
+        mBounds = bounds;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
             mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -468,7 +481,21 @@
         }
     }
 
-    void remove() {
+    /**
+     * Moves the stack to specified display.
+     * @param activityDisplay Target display to move the stack to.
+     */
+    void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
+        removeFromDisplay();
+        final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
+        postAddToDisplay(activityDisplay, bounds);
+    }
+
+    /**
+     * Updates the inner state of the stack to remove it from its current parent, so it can be
+     * either destroyed completely or re-parented.
+     */
+    private void removeFromDisplay() {
         mDisplayId = Display.INVALID_DISPLAY;
         mStacks = null;
         if (mTaskPositioner != null) {
@@ -480,6 +507,11 @@
             mStackSupervisor.resizeDockedStackLocked(
                     null, null, null, null, null, PRESERVE_WINDOWS);
         }
+    }
+
+    /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
+    void remove() {
+        removeFromDisplay();
         mStackSupervisor.deleteActivityContainerRecord(mStackId);
         mWindowManager.removeStack(mStackId);
         onParentChanged();
@@ -696,11 +728,7 @@
         }
 
         mStacks.add(addIndex, this);
-
-        // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
-        if (isOnHomeDisplay()) {
-            mStackSupervisor.setFocusStackUnchecked(reason, this);
-        }
+        mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
             insertTaskAtTop(task, null);
             return;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index eed8dd7..6167671 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2300,7 +2300,7 @@
 
         ActivityContainer activityContainer = new ActivityContainer(stackId);
         mActivityContainers.put(stackId, activityContainer);
-        activityContainer.attachToDisplayLocked(activityDisplay, onTop);
+        activityContainer.addToDisplayLocked(activityDisplay, onTop);
         return activityContainer.mStack;
     }
 
@@ -2364,6 +2364,40 @@
     }
 
     /**
+     * Move stack with all its existing content to specified display.
+     * @param stackId Id of stack to move.
+     * @param displayId Id of display to move stack to.
+     */
+    void moveStackToDisplayLocked(int stackId, int displayId) {
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        if (activityDisplay == null) {
+            throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
+                    + displayId);
+        }
+        final ActivityContainer activityContainer = mActivityContainers.get(stackId);
+        if (activityContainer != null) {
+            if (activityContainer.isAttachedLocked()) {
+                if (activityContainer.getDisplayId() == displayId) {
+                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
+                            + " to its current displayId=" + displayId);
+                }
+
+                activityContainer.moveToDisplayLocked(activityDisplay);
+            } else {
+                throw new IllegalStateException("moveStackToDisplayLocked: Stack with stackId="
+                        + stackId + " is not attached to any display.");
+            }
+        } else {
+            throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
+                    + stackId);
+        }
+
+        ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
+                !PRESERVE_WINDOWS);
+        // TODO(multi-display): resize stacks properly if moved from split-screen.
+    }
+
+    /**
      * Moves the specified task record to the input stack id.
      * WARNING: This method performs an unchecked/raw move of the task and
      * can leave the system in an unstable state if used incorrectly.
@@ -4032,22 +4066,33 @@
             }
         }
 
-        void attachToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
-            if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
+        /**
+         * Adds the stack to specified display. Also calls WindowManager to do the same from
+         * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+         * @param activityDisplay The display to add the stack to.
+         * @param onTop If true the stack will be place at the top of the display, else at the
+         *              bottom.
+         */
+        void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
                     + " to display=" + activityDisplay + " onTop=" + onTop);
+            if (mActivityDisplay != null) {
+                throw new IllegalStateException("ActivityContainer is already attached, " +
+                        "displayId=" + mActivityDisplay.mDisplayId);
+            }
             mActivityDisplay = activityDisplay;
-            mStack.attachDisplay(activityDisplay, onTop);
+            mStack.addToDisplay(activityDisplay, onTop);
             activityDisplay.attachActivities(mStack, onTop);
         }
 
         @Override
-        public void attachToDisplay(int displayId) {
+        public void addToDisplay(int displayId) {
             synchronized (mService) {
                 ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
                 if (activityDisplay == null) {
                     return;
                 }
-                attachToDisplayLocked(activityDisplay, true);
+                addToDisplayLocked(activityDisplay, true);
             }
         }
 
@@ -4103,16 +4148,44 @@
             }
         }
 
+        /** Remove the stack completely. */
         void removeLocked() {
             if (DEBUG_STACK) Slog.d(TAG_STACK, "removeLocked: " + this + " from display="
                     + mActivityDisplay + " Callers=" + Debug.getCallers(2));
             if (mActivityDisplay != null) {
-                mActivityDisplay.detachActivitiesLocked(mStack);
-                mActivityDisplay = null;
+                removeFromDisplayLocked();
             }
             mStack.remove();
         }
 
+        /**
+         * Remove the stack from its current {@link ActivityDisplay}, so it can be either destroyed
+         * completely or re-parented.
+         */
+        private void removeFromDisplayLocked() {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
+                    + " current displayId=" + mActivityDisplay.mDisplayId);
+
+            mActivityDisplay.detachActivitiesLocked(mStack);
+            mActivityDisplay = null;
+        }
+
+        /**
+         * Move the stack to specified display.
+         * @param activityDisplay Target display to move the stack to.
+         */
+        void moveToDisplayLocked(ActivityDisplay activityDisplay) {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "moveToDisplayLocked: " + this + " from display="
+                    + mActivityDisplay + " to display=" + activityDisplay
+                    + " Callers=" + Debug.getCallers(2));
+
+            removeFromDisplayLocked();
+
+            mActivityDisplay = activityDisplay;
+            mStack.moveToDisplay(activityDisplay);
+            activityDisplay.attachActivities(mStack, ON_TOP);
+        }
+
         @Override
         public final int startActivity(Intent intent) {
             return mService.startActivity(intent, this);
@@ -4232,7 +4305,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                attachToDisplayLocked(virtualActivityDisplay, true);
+                addToDisplayLocked(virtualActivityDisplay, true);
             }
 
             if (mSurface != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f351f6..87f1cf8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3109,14 +3109,28 @@
         boolean success =
             handleDeviceConnection(connected, outDevice, address, btDeviceName) &&
             handleDeviceConnection(connected, inDevice, address, btDeviceName);
-        if (success) {
-            synchronized (mScoClients) {
-                if (connected) {
-                    mBluetoothHeadsetDevice = btDevice;
-                } else {
-                    mBluetoothHeadsetDevice = null;
-                    resetBluetoothSco();
-                }
+
+        if (!success) {
+          return;
+        }
+
+        /* When one BT headset is disconnected while another BT headset
+         * is connected, don't mess with the headset device.
+         */
+        if ((state == BluetoothProfile.STATE_DISCONNECTED ||
+            state == BluetoothProfile.STATE_DISCONNECTING) &&
+            mBluetoothHeadset != null &&
+            mBluetoothHeadset.getAudioState(btDevice) == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+            Log.w(TAG, "SCO connected through another device, returning");
+            return;
+        }
+
+        synchronized (mScoClients) {
+            if (connected) {
+                mBluetoothHeadsetDevice = btDevice;
+            } else {
+                mBluetoothHeadsetDevice = null;
+                resetBluetoothSco();
             }
         }
     }
@@ -5264,7 +5278,6 @@
                 state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
                                                BluetoothProfile.STATE_DISCONNECTED);
                 BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
                 setBtScoDeviceConnectionState(btDevice, state);
             } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                 boolean broadcast = false;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2ca0db4..b2a5966 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2549,11 +2549,11 @@
             return 2;
         }
         switch (type) {
-        case TYPE_PRESENTATION:
-        case TYPE_PRIVATE_PRESENTATION:
-            return 2;
         case TYPE_WALLPAPER:
             // wallpaper is at the bottom, though the window manager may move it.
+            return 1;
+        case TYPE_PRESENTATION:
+        case TYPE_PRIVATE_PRESENTATION:
             return 2;
         case TYPE_DOCK_DIVIDER:
             return 2;
@@ -5089,12 +5089,12 @@
 
         // Dock windows carve out the bottom of the screen, so normal windows
         // can't appear underneath them.
-        if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw()
-                && win.isDisplayedLw() && !win.getGivenInsetsPendingLw()) {
+        if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
+                && !win.getGivenInsetsPendingLw()) {
             setLastInputMethodWindowLw(null, null);
             offsetInputMethodWindowLw(win);
         }
-        if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
+        if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
                 && !win.getGivenInsetsPendingLw()) {
             offsetVoiceInputWindowLw(win);
         }
@@ -5168,12 +5168,11 @@
     @Override
     public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
             WindowState attached, WindowState imeTarget) {
-        if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisibleOrBehindKeyguardLw="
-                + win.isVisibleOrBehindKeyguardLw());
+        final boolean visible = win.isVisibleLw();
+        if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": isVisible=" + visible);
         applyKeyguardPolicyLw(win, imeTarget);
         final int fl = PolicyControl.getWindowFlags(win, attrs);
-        if (mTopFullscreenOpaqueWindowState == null
-                && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) {
+        if (mTopFullscreenOpaqueWindowState == null && visible && attrs.type == TYPE_INPUT_METHOD) {
             mForcingShowNavBar = true;
             mForcingShowNavBarLayer = win.getSurfaceLayer();
         }
@@ -5189,8 +5188,7 @@
         boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                 && attrs.type < FIRST_SYSTEM_WINDOW;
         final int stackId = win.getStackId();
-        if (mTopFullscreenOpaqueWindowState == null &&
-                win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) {
+        if (mTopFullscreenOpaqueWindowState == null && visible) {
             if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
                 mForceStatusBar = true;
             }
@@ -5221,10 +5219,8 @@
             }
         }
 
-        final boolean reallyVisible = win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw();
-
         // Voice interaction overrides both top fullscreen and top docked.
-        if (reallyVisible && win.getAttrs().type == TYPE_VOICE_INTERACTION) {
+        if (visible && win.getAttrs().type == TYPE_VOICE_INTERACTION) {
             if (mTopFullscreenOpaqueWindowState == null) {
                 mTopFullscreenOpaqueWindowState = win;
                 if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
@@ -5240,7 +5236,7 @@
         }
 
         // Keep track of the window if it's dimming but not necessarily fullscreen.
-        if (mTopFullscreenOpaqueOrDimmingWindowState == null && reallyVisible
+        if (mTopFullscreenOpaqueOrDimmingWindowState == null && visible
                 && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
             mTopFullscreenOpaqueOrDimmingWindowState = win;
         }
@@ -5248,7 +5244,7 @@
         // We need to keep track of the top "fullscreen" opaque window for the docked stack
         // separately, because both the "real fullscreen" opaque window and the one for the docked
         // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
-        if (mTopDockedOpaqueWindowState == null && reallyVisible && appWindow && attached == null
+        if (mTopDockedOpaqueWindowState == null && visible && appWindow && attached == null
                 && isFullscreen(attrs) && stackId == DOCKED_STACK_ID) {
             mTopDockedOpaqueWindowState = win;
             if (mTopDockedOpaqueOrDimmingWindowState == null) {
@@ -5258,7 +5254,7 @@
 
         // Also keep track of any windows that are dimming but not necessarily fullscreen in the
         // docked stack.
-        if (mTopDockedOpaqueOrDimmingWindowState == null && reallyVisible && win.isDimming()
+        if (mTopDockedOpaqueOrDimmingWindowState == null && visible && win.isDimming()
                 && stackId == DOCKED_STACK_ID) {
             mTopDockedOpaqueOrDimmingWindowState = win;
         }
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 353d663..62f5468 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1014,6 +1014,9 @@
                     synchronized (mDeviceLockedForUser) {
                         mDeviceLockedForUser.delete(userId);
                     }
+                    synchronized (mTrustUsuallyManagedForUser) {
+                        mTrustUsuallyManagedForUser.delete(userId);
+                    }
                     refreshAgentList(userId);
                     refreshDeviceLockedForUser(userId);
                 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 243c4a5..ec4cdf2 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,6 +53,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
@@ -74,6 +76,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -137,9 +140,6 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -154,14 +154,24 @@
     /** Unique identifier of this stack. */
     private final int mDisplayId;
 
-    // The display only has 2 child window containers. mTaskStackContainers which contains all
-    // window containers that are related to apps (Activities) and mNonAppWindowContainers which
-    // contains all window containers not related to apps (e.g. Status bar).
+    /** The containers below are the only child containers the display can have. */
+    // Contains all window containers that are related to apps (Activities)
     private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
-    private final NonAppWindowContainers mNonAppWindowContainers = new NonAppWindowContainers();
+    // Contains all non-app window containers that should be displayed above the app containers
+    // (e.g. Status bar)
+    private final NonAppWindowContainers mAboveAppWindowsContainers =
+            new NonAppWindowContainers("mAboveAppWindowsContainers");
+    // Contains all non-app window containers that should be displayed below the app containers
+    // (e.g. Wallpaper).
+    private final NonAppWindowContainers mBelowAppWindowsContainers =
+            new NonAppWindowContainers("mBelowAppWindowsContainers");
+    // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
+    // on the IME target. We mainly have this container grouping so we can keep track of all the IME
+    // window containers together and move them in-sync if/when needed.
+    private final NonAppWindowContainers mImeWindowsContainers =
+            new NonAppWindowContainers("mImeWindowsContainers");
 
-    /** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element
-     * from mDisplayWindows; */
+    // Z-ordered (bottom-most first) list of all Window objects.
     private final WindowList mWindows = new WindowList();
 
     // Mapping from a token IBinder to a WindowToken object on this display.
@@ -184,6 +194,7 @@
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
     int pendingLayoutChanges;
+    // TODO(multi-display): remove some of the usages.
     final boolean isDefaultDisplay;
 
     /** Window tokens that are in the process of exiting, but still on screen for animations. */
@@ -266,8 +277,10 @@
         mDimLayerController = new DimLayerController(this);
 
         // These are the only direct children we should ever have and they are permanent.
+        super.addChild(mBelowAppWindowsContainers, null);
         super.addChild(mTaskStackContainers, null);
-        super.addChild(mNonAppWindowContainers, null);
+        super.addChild(mAboveAppWindowsContainers, null);
+        super.addChild(mImeWindowsContainers, null);
     }
 
     int getDisplayId() {
@@ -301,14 +314,36 @@
         if (token.asAppWindowToken() == null) {
             // Add non-app token to container hierarchy on the display. App tokens are added through
             // the parent container managing them (e.g. Tasks).
-            mNonAppWindowContainers.addChild(token, null);
+            switch (token.windowType) {
+                case TYPE_WALLPAPER:
+                    mBelowAppWindowsContainers.addChild(token);
+                    break;
+                case TYPE_INPUT_METHOD:
+                case TYPE_INPUT_METHOD_DIALOG:
+                    mImeWindowsContainers.addChild(token);
+                    break;
+                default:
+                    mAboveAppWindowsContainers.addChild(token);
+                    break;
+            }
         }
     }
 
     WindowToken removeWindowToken(IBinder binder) {
         final WindowToken token = mTokenMap.remove(binder);
         if (token != null && token.asAppWindowToken() == null) {
-            mNonAppWindowContainers.removeChild(token);
+            switch (token.windowType) {
+                case TYPE_WALLPAPER:
+                    mBelowAppWindowsContainers.removeChild(token);
+                    break;
+                case TYPE_INPUT_METHOD:
+                case TYPE_INPUT_METHOD_DIALOG:
+                    mImeWindowsContainers.removeChild(token);
+                    break;
+                default:
+                    mAboveAppWindowsContainers.removeChild(token);
+                    break;
+            }
         }
         return token;
     }
@@ -535,9 +570,72 @@
         out.set(mContentRect);
     }
 
-    /** Refer to {@link WindowManagerService#attachStack(int, int, boolean)} */
-    void attachStack(TaskStack stack, boolean onTop) {
-        mTaskStackContainers.attachStack(stack, onTop);
+    /**
+     * Adds the stack to this display.
+     * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+     */
+    Rect addStackToDisplay(int stackId, boolean onTop) {
+        boolean attachedToDisplay = false;
+        TaskStack stack = mService.mStackIdToStack.get(stackId);
+        if (stack == null) {
+            if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+                    + mDisplayId);
+
+            stack = getStackById(stackId);
+            if (stack != null) {
+                // It's already attached to the display...clear mDeferRemoval and move stack to
+                // appropriate z-order on display as needed.
+                stack.mDeferRemoval = false;
+                moveStack(stack, onTop);
+                attachedToDisplay = true;
+            } else {
+                stack = new TaskStack(mService, stackId);
+            }
+
+            mService.mStackIdToStack.put(stackId, stack);
+            if (stackId == DOCKED_STACK_ID) {
+                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
+            }
+        } else {
+            final DisplayContent currentDC = stack.getDisplayContent();
+            if (currentDC != null) {
+                throw new IllegalStateException("Trying to add stackId=" + stackId
+                        + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
+                        + currentDC.getDisplayId());
+            }
+        }
+
+        if (!attachedToDisplay) {
+            mTaskStackContainers.addStackToDisplay(stack, onTop);
+        }
+
+        if (stack.getRawFullscreen()) {
+            return null;
+        }
+        final Rect bounds = new Rect();
+        stack.getRawBounds(bounds);
+        return bounds;
+    }
+
+    /** Removes the stack from the display and prepares for changing the parent. */
+    private void removeStackFromDisplay(TaskStack stack) {
+        mTaskStackContainers.removeStackFromDisplay(stack);
+    }
+
+    /** Moves the stack to this display and returns the updated bounds. */
+    Rect moveStackToDisplay(TaskStack stack) {
+        final DisplayContent currentDisplayContent = stack.getDisplayContent();
+        if (currentDisplayContent == null) {
+            throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
+                    + " which is not currently attached to any display");
+        }
+        if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+            throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
+                    + " to its current displayId=" + mDisplayId);
+        }
+
+        currentDisplayContent.removeStackFromDisplay(stack);
+        return addStackToDisplay(stack.mStackId, true /* onTop */);
     }
 
     void moveStack(TaskStack stack, boolean toTop) {
@@ -2394,7 +2492,11 @@
             if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
                     pendingLayoutChanges);
 
-            if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+            // TODO(multi-display): For now adjusting wallpaper only on primary display to avoid
+            // the wallpaper window jumping across displays.
+            // Remove check for default display when there will be support for multiple wallpaper
+            // targets (on different displays).
+            if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
                 adjustWallpaperWindows();
             }
 
@@ -3076,9 +3178,13 @@
      * Window container class that contains all containers on this display relating to Apps.
      * I.e Activities.
      */
-    private class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+    private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
 
-        void attachStack(TaskStack stack, boolean onTop) {
+        /**
+         * Adds the stack to this container.
+         * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+         */
+        void addStackToDisplay(TaskStack stack, boolean onTop) {
             if (stack.mStackId == HOME_STACK_ID) {
                 if (mHomeStack != null) {
                     throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
@@ -3089,6 +3195,21 @@
             stack.onDisplayChanged(DisplayContent.this);
         }
 
+        /** Removes the stack from its container and prepare for changing the parent. */
+        void removeStackFromDisplay(TaskStack stack) {
+            removeChild(stack);
+            stack.onRemovedFromDisplay();
+            // TODO: remove when window list will be gone.
+            // Manually remove records from window list and tap excluded windows list.
+            for (int i = mWindows.size() - 1; i >= 0; --i) {
+                final WindowState windowState = mWindows.get(i);
+                if (stack == windowState.getStack()) {
+                    mWindows.remove(i);
+                    mTapExcludedWindows.remove(windowState);
+                }
+            }
+        }
+
         void moveStack(TaskStack stack, boolean toTop) {
             if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
                 // This stack is always-on-top silly...
@@ -3158,7 +3279,28 @@
      * Window container class that contains all containers on this display that are not related to
      * Apps. E.g. status bar.
      */
-    private static class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+    private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+        /**
+         * Compares two child window tokens returns -1 if the first is lesser than the second in
+         * terms of z-order and 1 otherwise.
+         */
+        final Comparator<WindowToken> mWindowComparator = (token1, token2) ->
+                // Tokens with higher base layer are z-ordered on-top.
+                mService.mPolicy.windowTypeToLayerLw(token1.windowType)
+                < mService.mPolicy.windowTypeToLayerLw(token2.windowType) ? -1 : 1;
 
+        private final String mName;
+        NonAppWindowContainers(String name) {
+            mName = name;
+        }
+
+        void addChild(WindowToken token) {
+            addChild(token, mWindowComparator);
+        }
+
+        @Override
+        String getName() {
+            return mName;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6325cda..f038135 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -215,49 +215,6 @@
         return dc;
     }
 
-    /** Adds the input stack id to the input display id and returns the bounds of the added stack.*/
-    Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
-        final DisplayContent dc = getDisplayContent(displayId);
-        if (dc == null) {
-            Slog.w(TAG_WM, "addStackToDisplay: Trying to add stackId=" + stackId
-                    + " to unknown displayId=" + displayId + " callers=" + Debug.getCallers(6));
-            return null;
-        }
-
-        boolean attachedToDisplay = false;
-        TaskStack stack = mService.mStackIdToStack.get(stackId);
-        if (stack == null) {
-            if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);
-
-            stack = dc.getStackById(stackId);
-            if (stack != null) {
-                // It's already attached to the display...clear mDeferRemoval and move stack to
-                // appropriate z-order on display as needed.
-                stack.mDeferRemoval = false;
-                dc.moveStack(stack, onTop);
-                attachedToDisplay = true;
-            } else {
-                stack = new TaskStack(mService, stackId);
-            }
-
-            mService.mStackIdToStack.put(stackId, stack);
-            if (stackId == DOCKED_STACK_ID) {
-                dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
-            }
-        }
-
-        if (!attachedToDisplay) {
-            dc.attachStack(stack, onTop);
-        }
-
-        if (stack.getRawFullscreen()) {
-            return null;
-        }
-        final Rect bounds = new Rect();
-        stack.getRawBounds(bounds);
-        return bounds;
-    }
-
     boolean isLayoutNeeded() {
         final int numDisplays = mChildren.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 60ba025..a0270c6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -793,6 +793,14 @@
     void removeImmediately() {
         super.removeImmediately();
 
+        onRemovedFromDisplay();
+    }
+
+    /**
+     * Removes the stack it from its current parent, so it can be either destroyed completely or
+     * re-parented.
+     */
+    void onRemovedFromDisplay() {
         mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
@@ -800,15 +808,13 @@
             mAnimationBackgroundSurface.destroySurface();
             mAnimationBackgroundSurface = null;
         }
-        final DockedStackDividerController dividerController =
-                mDisplayContent.mDividerControllerLocked;
-        mDisplayContent = null;
-
-        mService.mWindowPlacerLocked.requestTraversal();
 
         if (mStackId == DOCKED_STACK_ID) {
-            dividerController.notifyDockedStackExistsChanged(false);
+            mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
         }
+
+        mDisplayContent = null;
+        mService.mWindowPlacerLocked.requestTraversal();
     }
 
     void resetAnimationBackgroundAnimator() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0abcd9f..7723752 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1219,7 +1219,7 @@
                         return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                     }
                 }
-                token = new WindowToken(this, attrs.token, -1, false, displayContent);
+                token = new WindowToken(this, attrs.token, type, false, displayContent);
             } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                 atoken = token.asAppWindowToken();
                 if (atoken == null) {
@@ -1287,7 +1287,7 @@
                 // It is not valid to use an app token with other system types; we will
                 // instead make a new token for it (as if null had been passed in for the token).
                 attrs.token = null;
-                token = new WindowToken(this, null, -1, false, displayContent);
+                token = new WindowToken(this, null, type, false, displayContent);
             }
 
             WindowState win = new WindowState(this, session, client, token, parentWindow,
@@ -2721,11 +2721,11 @@
     }
 
     void setFocusTaskRegionLocked() {
-        if (mFocusedApp != null) {
-            final Task task = mFocusedApp.mTask;
-            final DisplayContent displayContent = task.getDisplayContent();
+        final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
+        if (focusedTask != null) {
+            final DisplayContent displayContent = focusedTask.getDisplayContent();
             if (displayContent != null) {
-                displayContent.setTouchExcludeRegion(task);
+                displayContent.setTouchExcludeRegion(focusedTask);
             }
         }
     }
@@ -3425,24 +3425,63 @@
     }
 
     /**
-     * Create a new TaskStack and place it on a DisplayContent.
+     * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
+     * specified stackId.
      * @param stackId The unique identifier of the new stack.
      * @param displayId The unique identifier of the DisplayContent.
      * @param onTop If true the stack will be place at the top of the display,
-     *              else at the bottom
-     * @return The initial bounds the stack was created with. null means fullscreen.
+     *              else at the bottom.
+     * @return The bounds that the stack has after adding. null means fullscreen.
      */
-    public Rect attachStack(int stackId, int displayId, boolean onTop) {
+    public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mWindowMap) {
-                return mRoot.addStackToDisplay(stackId, displayId, onTop);
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                            + " to unknown displayId=" + displayId);
+                }
+
+                return dc.addStackToDisplay(stackId, onTop);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    /**
+     * Move a TaskStack from current DisplayContent to specified one.
+     * @param stackId The unique identifier of the new stack.
+     * @param displayId The unique identifier of the new display.
+     */
+    public Rect moveStackToDisplay(int stackId, int displayId) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                TaskStack stack = mStackIdToStack.get(stackId);
+                if (stack == null) {
+                    throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
+                            + " to displayId=" + displayId);
+                }
+
+                final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
+                if (targetDisplayContent == null) {
+                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
+                            + " to unknown displayId=" + displayId);
+                }
+
+                return targetDisplayContent.moveStackToDisplay(stack);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * Remove a TaskStack completely.
+     * @param stackId The unique identifier of the stack.
+     */
     public void removeStack(int stackId) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 534a3d2..972c359 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1353,25 +1353,6 @@
     }
 
     /**
-     * Like {@link #isVisibleLw}, but also counts a window that is currently "hidden" behind the
-     * keyguard as visible.  This allows us to apply things like window flags that impact the
-     * keyguard. XXX I am starting to think we need to have ANOTHER visibility flag for this
-     * "hidden behind keyguard" state rather than overloading mPolicyVisibility.  Ungh.
-     */
-    @Override
-    public boolean isVisibleOrBehindKeyguardLw() {
-        if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
-            return false;
-        }
-        final AppWindowToken atoken = mAppToken;
-        final boolean animating = atoken != null && atoken.mAppAnimator.animation != null;
-        return mHasSurface && !mDestroying && !mAnimatingExit
-                && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
-                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
-                        || mWinAnimator.mAnimation != null || animating);
-    }
-
-    /**
      * Is this window visible, ignoring its app token? It is not visible if there is no surface,
      * or we are in the process of running an exit animation that will remove the surface.
      */
@@ -1418,14 +1399,6 @@
      * being visible.
      */
     boolean isOnScreen() {
-        return mPolicyVisibility && isOnScreenIgnoringKeyguard();
-    }
-
-    /**
-     * Like isOnScreen(), but ignores any force hiding of the window due
-     * to the keyguard.
-     */
-    private boolean isOnScreenIgnoringKeyguard() {
         if (!mHasSurface || mDestroying) {
             return false;
         }
@@ -1449,7 +1422,7 @@
     boolean mightAffectAllDrawn(boolean visibleOnly) {
         final boolean isViewVisible = (mAppToken == null || !mAppToken.clientHidden)
                 && (mViewVisibility == View.VISIBLE) && !mWindowRemovalAllowed;
-        return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible)
+        return (isOnScreen() && (!visibleOnly || isViewVisible)
                 || mWinAnimator.mAttrType == TYPE_BASE_APPLICATION
                 || mWinAnimator.mAttrType == TYPE_DRAWN_APPLICATION)
                 && !mAnimatingExit && !mDestroying;
@@ -1479,28 +1452,6 @@
     }
 
     /**
-     * Like isReadyForDisplay(), but ignores any force hiding of the window due
-     * to the keyguard.
-     */
-    boolean isReadyForDisplayIgnoringKeyguard() {
-        if (mToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
-            return false;
-        }
-        final AppWindowToken atoken = mAppToken;
-        if (atoken == null && !mPolicyVisibility) {
-            // If this is not an app window, and the policy has asked to force
-            // hide, then we really do want to hide.
-            return false;
-        }
-        return mHasSurface && !mDestroying
-                && ((!isParentWindowHidden() && mViewVisibility == View.VISIBLE && !mToken.hidden)
-                        || mWinAnimator.mAnimation != null
-                        || ((atoken != null) && (atoken.mAppAnimator.animation != null)
-                                && !mWinAnimator.isDummyAnimation())
-                        || isAnimatingWithSavedSurface());
-    }
-
-    /**
      * Like isOnScreen, but returns false if the surface hasn't yet
      * been drawn.
      */
@@ -3771,7 +3722,7 @@
 
         logPerformShow("performShow on ");
 
-        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplayIgnoringKeyguard()) {
+        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
             return false;
         }
 
@@ -3821,7 +3772,7 @@
                 || (DEBUG_STARTING_WINDOW && mAttrs.type == TYPE_APPLICATION_STARTING)) {
             Slog.v(TAG, prefix + this
                     + ": mDrawState=" + mWinAnimator.drawStateToString()
-                    + " readyForDisplay=" + isReadyForDisplayIgnoringKeyguard()
+                    + " readyForDisplay=" + isReadyForDisplay()
                     + " starting=" + (mAttrs.type == TYPE_APPLICATION_STARTING)
                     + " during animation: policyVis=" + mPolicyVisibility
                     + " parentHidden=" + isParentWindowHidden()
diff --git a/services/tests/runtests.py b/services/tests/runtests.py
index 35fec90f..7980dc2 100755
--- a/services/tests/runtests.py
+++ b/services/tests/runtests.py
@@ -61,8 +61,8 @@
 
     print 'Running tests...'
     if len(sys.argv) != 1:
-        run('adb shell am instrument -w "%s" %s' %
-            (INSTRUMENTED_PACKAGE_RUNNER, ' '.join(sys.argv[1:])))
+        run('adb shell am instrument -w %s "%s"' %
+            (' '.join(sys.argv[1:]), INSTRUMENTED_PACKAGE_RUNNER))
         return 0
 
     # It would be nice if the activity manager accepted a list of packages, but
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
new file mode 100644
index 0000000..cb5e8bb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.MagnificationSpec;
+import android.view.WindowManagerInternal;
+import android.view.WindowManagerInternal.MagnificationCallbacks;
+
+import com.android.internal.R;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class MagnificationControllerTest {
+    static final Rect INITIAL_MAGNIFICATION_BOUNDS = new Rect(0, 0, 100, 200);
+    static final PointF INITIAL_MAGNIFICATION_BOUNDS_CENTER = new PointF(
+            INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY());
+    static final PointF INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER = new PointF(25, 50);
+    static final PointF INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(75, 150);
+    static final Rect OTHER_MAGNIFICATION_BOUNDS = new Rect(100, 200, 500, 600);
+    static final PointF OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER = new PointF(400, 500);
+    static final Region INITIAL_MAGNIFICATION_REGION = new Region(INITIAL_MAGNIFICATION_BOUNDS);
+    static final Region OTHER_REGION = new Region(OTHER_MAGNIFICATION_BOUNDS);
+    static final int SERVICE_ID_1 = 1;
+    static final int SERVICE_ID_2 = 2;
+
+    final Context mMockContext = mock(Context.class);
+    final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class);
+    final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class);
+    final MessageCapturingHandler mMessageCapturingHandler =
+            new MessageCapturingHandler(new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            return mMagnificationController.handleMessage(msg);
+        }
+    });
+    final ArgumentCaptor<MagnificationSpec> mMagnificationSpecCaptor =
+            ArgumentCaptor.forClass(MagnificationSpec.class);
+    final ValueAnimator mMockValueAnimator = mock(ValueAnimator.class);
+    MagnificationController.SettingsBridge mMockSettingsBridge;
+
+
+    MagnificationController mMagnificationController;
+    ValueAnimator.AnimatorUpdateListener mTargetAnimationListener;
+
+    @BeforeClass
+    public static void oneTimeInitialization() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
+        Resources mockResources = mock(Resources.class);
+        when(mMockContext.getResources()).thenReturn(mockResources);
+        when(mockResources.getInteger(R.integer.config_longAnimTime))
+                .thenReturn(1000);
+        mMockSettingsBridge = mock(MagnificationController.SettingsBridge.class);
+        mMagnificationController = new MagnificationController(mMockContext, mMockAms, new Object(),
+                mMessageCapturingHandler, mMockWindowManager, mMockValueAnimator,
+                mMockSettingsBridge);
+
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocationOnMock) throws Throwable {
+                Object[] args = invocationOnMock.getArguments();
+                Region regionArg = (Region) args[0];
+                regionArg.set(INITIAL_MAGNIFICATION_REGION);
+                return null;
+            }
+        }).when(mMockWindowManager).getMagnificationRegion((Region) anyObject());
+
+        ArgumentCaptor<ValueAnimator.AnimatorUpdateListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(ValueAnimator.AnimatorUpdateListener.class);
+        verify(mMockValueAnimator).addUpdateListener(listenerArgumentCaptor.capture());
+        mTargetAnimationListener = listenerArgumentCaptor.getValue();
+        Mockito.reset(mMockValueAnimator); // Ignore other initialization
+    }
+
+    @Test
+    public void testRegister_WindowManagerAndContextRegisterListeners() {
+        mMagnificationController.register();
+        verify(mMockContext).registerReceiver(
+                (BroadcastReceiver) anyObject(), (IntentFilter) anyObject());
+        verify(mMockWindowManager).setMagnificationCallbacks((MagnificationCallbacks) anyObject());
+        assertTrue(mMagnificationController.isRegisteredLocked());
+    }
+
+    @Test
+    public void testRegister_WindowManagerAndContextUnregisterListeners() {
+        mMagnificationController.register();
+        mMagnificationController.unregister();
+
+        verify(mMockContext).unregisterReceiver((BroadcastReceiver) anyObject());
+        verify(mMockWindowManager).setMagnificationCallbacks(null);
+        assertFalse(mMagnificationController.isRegisteredLocked());
+    }
+
+    @Test
+    public void testInitialState_noMagnificationAndMagnificationRegionReadFromWindowManager() {
+        mMagnificationController.register();
+        MagnificationSpec expectedInitialSpec = getMagnificationSpec(1.0f, 0.0f, 0.0f);
+        Region initialMagRegion = new Region();
+        Rect initialBounds = new Rect();
+
+        assertEquals(expectedInitialSpec, getCurrentMagnificationSpec());
+        mMagnificationController.getMagnificationRegion(initialMagRegion);
+        mMagnificationController.getMagnificationBounds(initialBounds);
+        assertEquals(INITIAL_MAGNIFICATION_REGION, initialMagRegion);
+        assertEquals(INITIAL_MAGNIFICATION_BOUNDS, initialBounds);
+        assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerX(),
+                mMagnificationController.getCenterX(), 0.0f);
+        assertEquals(INITIAL_MAGNIFICATION_BOUNDS.centerY(),
+                mMagnificationController.getCenterY(), 0.0f);
+    }
+
+    @Test
+    public void testNotRegistered_publicMethodsShouldBeBenign() {
+        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.magnificationRegionContains(100, 100));
+        assertFalse(mMagnificationController.reset(true));
+        assertFalse(mMagnificationController.setScale(2, 100, 100, true, 0));
+        assertFalse(mMagnificationController.setCenter(100, 100, false, 1));
+        assertFalse(mMagnificationController.setScaleAndCenter(1.5f, 100, 100, false, 2));
+        assertTrue(mMagnificationController.getIdOfLastServiceToMagnify() < 0);
+
+        mMagnificationController.getMagnificationRegion(new Region());
+        mMagnificationController.getMagnificationBounds(new Rect());
+        mMagnificationController.getScale();
+        mMagnificationController.getOffsetX();
+        mMagnificationController.getOffsetY();
+        mMagnificationController.getCenterX();
+        mMagnificationController.getCenterY();
+        mMagnificationController.offsetMagnifiedRegion(50, 50, 1);
+        mMagnificationController.unregister();
+    }
+
+    @Test
+    public void testSetScale_noAnimation_shouldGoStraightToWindowManagerAndUpdateState() {
+        mMagnificationController.register();
+        final float scale = 2.0f;
+        final PointF center = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        final PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, center, scale);
+        assertTrue(mMagnificationController
+                .setScale(scale, center.x, center.y, false, SERVICE_ID_1));
+
+        final MagnificationSpec expectedSpec = getMagnificationSpec(scale, offsets);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+        assertThat(getCurrentMagnificationSpec(), closeTo(expectedSpec));
+        assertEquals(center.x, mMagnificationController.getCenterX(), 0.0);
+        assertEquals(center.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockValueAnimator, times(0)).start();
+    }
+
+    @Test
+    public void testSetScale_withPivotAndAnimation_stateChangesAndAnimationHappens() {
+        mMagnificationController.register();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        float scale = 2.0f;
+        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        assertTrue(mMagnificationController
+                .setScale(scale, pivotPoint.x, pivotPoint.y, true, SERVICE_ID_1));
+
+        // New center should be halfway between original center and pivot
+        PointF newCenter = new PointF((pivotPoint.x + INITIAL_MAGNIFICATION_BOUNDS.centerX()) / 2,
+                (pivotPoint.y + INITIAL_MAGNIFICATION_BOUNDS.centerY()) / 2);
+        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
+
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+        verify(mMockValueAnimator).start();
+
+        // Initial value
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(startSpec);
+
+        // Intermediate point
+        Mockito.reset(mMockWindowManager);
+        float fraction = 0.5f;
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(
+                argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
+
+        // Final value
+        Mockito.reset(mMockWindowManager);
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+    }
+
+    @Test
+    public void testSetCenter_whileMagnifying_noAnimation_centerMoves() {
+        mMagnificationController.register();
+        // First zoom in
+        float scale = 2.0f;
+        assertTrue(mMagnificationController.setScale(scale,
+                INITIAL_MAGNIFICATION_BOUNDS.centerX(), INITIAL_MAGNIFICATION_BOUNDS.centerY(),
+                false, SERVICE_ID_1));
+        Mockito.reset(mMockWindowManager);
+
+        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        assertTrue(mMagnificationController
+                .setCenter(newCenter.x, newCenter.y, false, SERVICE_ID_1));
+        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        MagnificationSpec expectedSpec = getMagnificationSpec(scale, expectedOffsets);
+
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockValueAnimator, times(0)).start();
+    }
+
+    @Test
+    public void testSetScaleAndCenter_animated_stateChangesAndAnimationHappens() {
+        mMagnificationController.register();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        float scale = 2.5f;
+        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        MagnificationSpec endSpec = getMagnificationSpec(scale, offsets);
+
+        assertTrue(mMagnificationController.setScaleAndCenter(scale, newCenter.x, newCenter.y,
+                true, SERVICE_ID_1));
+
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+        assertThat(getCurrentMagnificationSpec(), closeTo(endSpec));
+        verify(mMockAms).notifyMagnificationChanged(
+                INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
+        verify(mMockValueAnimator).start();
+
+        // Initial value
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(startSpec);
+
+        // Intermediate point
+        Mockito.reset(mMockWindowManager);
+        float fraction = 0.33f;
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(
+                argThat(closeTo(getInterpolatedMagSpec(startSpec, endSpec, fraction))));
+
+        // Final value
+        Mockito.reset(mMockWindowManager);
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+    }
+
+    @Test
+    public void testSetScaleAndCenter_scaleOutOfBounds_cappedAtLimits() {
+        mMagnificationController.register();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        PointF offsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter,
+                MagnificationController.MAX_SCALE);
+        MagnificationSpec endSpec = getMagnificationSpec(
+                MagnificationController.MAX_SCALE, offsets);
+
+        assertTrue(mMagnificationController.setScaleAndCenter(
+                MagnificationController.MAX_SCALE + 1.0f,
+                newCenter.x, newCenter.y, false, SERVICE_ID_1));
+
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+        Mockito.reset(mMockWindowManager);
+
+        // Verify that we can't zoom below 1x
+        assertTrue(mMagnificationController.setScaleAndCenter(0.5f,
+                INITIAL_MAGNIFICATION_BOUNDS_CENTER.x, INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
+                false, SERVICE_ID_1));
+
+        assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.x,
+                mMagnificationController.getCenterX(), 0.5);
+        assertEquals(INITIAL_MAGNIFICATION_BOUNDS_CENTER.y,
+                mMagnificationController.getCenterY(), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+    }
+
+    @Test
+    public void testSetScaleAndCenter_centerOutOfBounds_cappedAtLimits() {
+        mMagnificationController.register();
+        float scale = 2.0f;
+
+        // Off the edge to the top and left
+        assertTrue(mMagnificationController.setScaleAndCenter(
+                scale, -100f, -200f, false, SERVICE_ID_1));
+
+        PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+        PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(
+                argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
+        Mockito.reset(mMockWindowManager);
+
+        // Off the edge to the bottom and right
+        assertTrue(mMagnificationController.setScaleAndCenter(scale,
+                INITIAL_MAGNIFICATION_BOUNDS.right + 1, INITIAL_MAGNIFICATION_BOUNDS.bottom + 1,
+                false, SERVICE_ID_1));
+        newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.5);
+        verify(mMockWindowManager).setMagnificationSpec(
+                argThat(closeTo(getMagnificationSpec(scale, newOffsets))));
+    }
+
+    @Test
+    public void testMagnificationRegionChanged_serviceNotified() {
+        mMagnificationController.register();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        callbacks.onMagnificationRegionChanged(OTHER_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        verify(mMockAms).notifyMagnificationChanged(OTHER_REGION, 1.0f,
+                OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY());
+    }
+
+    @Test
+    public void testOffsetMagnifiedRegion_whileMagnifying_offsetsMove() {
+        mMagnificationController.register();
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        PointF startOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, startCenter, scale);
+        // First zoom in
+        assertTrue(mMagnificationController
+                .setScaleAndCenter(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+        Mockito.reset(mMockWindowManager);
+
+        PointF newCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        PointF newOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale);
+        mMagnificationController.offsetMagnifiedRegion(
+                startOffsets.x - newOffsets.x, startOffsets.y - newOffsets.y, SERVICE_ID_1);
+
+        MagnificationSpec expectedSpec = getMagnificationSpec(scale, newOffsets);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedSpec)));
+        assertEquals(newCenter.x, mMagnificationController.getCenterX(), 0.0);
+        assertEquals(newCenter.y, mMagnificationController.getCenterY(), 0.0);
+        verify(mMockValueAnimator, times(0)).start();
+    }
+
+    @Test
+    public void testOffsetMagnifiedRegion_whileNotMagnifying_hasNoEffect() {
+        mMagnificationController.register();
+        Mockito.reset(mMockWindowManager);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+    @Test
+    public void testOffsetMagnifiedRegion_whileMagnifyingButAtEdge_hasNoEffect() {
+        mMagnificationController.register();
+        float scale = 2.0f;
+
+        // Upper left edges
+        PointF ulCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+        assertTrue(mMagnificationController
+                .setScaleAndCenter(scale, ulCenter.x, ulCenter.y, false, SERVICE_ID_1));
+        Mockito.reset(mMockWindowManager);
+        MagnificationSpec ulSpec = getCurrentMagnificationSpec();
+        mMagnificationController.offsetMagnifiedRegion(-10, -10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(), closeTo(ulSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+
+        // Lower right edges
+        PointF lrCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        assertTrue(mMagnificationController
+                .setScaleAndCenter(scale, lrCenter.x, lrCenter.y, false, SERVICE_ID_1));
+        Mockito.reset(mMockWindowManager);
+        MagnificationSpec lrSpec = getCurrentMagnificationSpec();
+        mMagnificationController.offsetMagnifiedRegion(10, 10, SERVICE_ID_1);
+        assertThat(getCurrentMagnificationSpec(), closeTo(lrSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+    @Test
+    public void testGetIdOfLastServiceToChange_returnsCorrectValue() {
+        mMagnificationController.register();
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        assertTrue(mMagnificationController
+                .setScale(2.0f, startCenter.x, startCenter.y, false, SERVICE_ID_1));
+        assertEquals(SERVICE_ID_1, mMagnificationController.getIdOfLastServiceToMagnify());
+        assertTrue(mMagnificationController
+                .setScale(1.5f, startCenter.x, startCenter.y, false, SERVICE_ID_2));
+        assertEquals(SERVICE_ID_2, mMagnificationController.getIdOfLastServiceToMagnify());
+    }
+
+    @Test
+    public void testSetUserId_resetsOnlyIfIdChanges() {
+        final int userId1 = 1;
+        final int userId2 = 2;
+
+        mMagnificationController.register();
+        mMagnificationController.setUserId(userId1);
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+
+        mMagnificationController.setUserId(userId1);
+        assertTrue(mMagnificationController.isMagnifying());
+        mMagnificationController.setUserId(userId2);
+        assertFalse(mMagnificationController.isMagnifying());
+    }
+
+    @Test
+    public void testResetIfNeeded_doesWhatItSays() {
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        assertTrue(mMagnificationController.resetIfNeeded(false));
+        verify(mMockAms).notifyMagnificationChanged(
+                eq(INITIAL_MAGNIFICATION_REGION), eq(1.0f), anyInt(), anyInt());
+        assertFalse(mMagnificationController.isMagnifying());
+        assertFalse(mMagnificationController.resetIfNeeded(false));
+    }
+
+    @Test
+    public void testTurnScreenOff_resetsMagnification() {
+        mMagnificationController.register();
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext).registerReceiver(
+                broadcastReceiverCaptor.capture(), (IntentFilter) anyObject());
+        BroadcastReceiver br = broadcastReceiverCaptor.getValue();
+        zoomIn2xToMiddle();
+        br.onReceive(mMockContext, null);
+        mMessageCapturingHandler.sendAllMessages();
+        assertFalse(mMagnificationController.isMagnifying());
+    }
+
+    @Test
+    public void testUserContextChange_resetsMagnification() {
+        mMagnificationController.register();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        zoomIn2xToMiddle();
+        callbacks.onUserContextChanged();
+        mMessageCapturingHandler.sendAllMessages();
+        assertFalse(mMagnificationController.isMagnifying());
+    }
+
+    @Test
+    public void testRotation_resetsMagnification() {
+        mMagnificationController.register();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        zoomIn2xToMiddle();
+        mMessageCapturingHandler.sendAllMessages();
+        assertTrue(mMagnificationController.isMagnifying());
+        callbacks.onRotationChanged(0);
+        mMessageCapturingHandler.sendAllMessages();
+        assertFalse(mMagnificationController.isMagnifying());
+    }
+
+    @Test
+    public void testBoundsChange_whileMagnifyingWithCompatibleSpec_noSpecChange() {
+        // Going from a small region to a large one leads to no issues
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        Mockito.reset(mMockWindowManager);
+        callbacks.onMagnificationRegionChanged(OTHER_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+    @Test
+    public void testBoundsChange_whileZoomingWithCompatibleSpec_noSpecChange() {
+        mMagnificationController.register();
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        Mockito.reset(mMockWindowManager);
+        callbacks.onMagnificationRegionChanged(OTHER_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+    @Test
+    public void testBoundsChange_whileMagnifyingWithIncompatibleSpec_offsetsConstrained() {
+        // In a large region, pan to the farthest point possible
+        mMagnificationController.register();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        callbacks.onMagnificationRegionChanged(OTHER_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        float scale = 2.0f;
+        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(startSpec)));
+        Mockito.reset(mMockWindowManager);
+
+        callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+
+        MagnificationSpec endSpec = getCurrentMagnificationSpec();
+        assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
+        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
+                INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
+        assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+    }
+
+    @Test
+    public void testBoundsChange_whileZoomingWithIncompatibleSpec_jumpsToCompatibleSpec() {
+        mMagnificationController.register();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        callbacks.onMagnificationRegionChanged(OTHER_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        PointF startCenter = OTHER_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        float scale = 2.0f;
+        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, true, SERVICE_ID_1);
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        when (mMockValueAnimator.isRunning()).thenReturn(true);
+
+        callbacks.onMagnificationRegionChanged(INITIAL_MAGNIFICATION_REGION);
+        mMessageCapturingHandler.sendAllMessages();
+        verify(mMockValueAnimator).cancel();
+
+        MagnificationSpec endSpec = getCurrentMagnificationSpec();
+        assertThat(endSpec, CoreMatchers.not(closeTo(startSpec)));
+        PointF expectedOffsets = computeOffsets(INITIAL_MAGNIFICATION_BOUNDS,
+                INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale);
+        assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets)));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(endSpec)));
+    }
+
+    @Test
+    public void testRequestRectOnScreen_rectAlreadyOnScreen_doesNothing() {
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        Mockito.reset(mMockWindowManager);
+        int centerX = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.x;
+        int centerY = (int) INITIAL_MAGNIFICATION_BOUNDS_CENTER.y;
+        callbacks.onRectangleOnScreenRequested(centerX - 1, centerY - 1, centerX + 1, centerY - 1);
+        mMessageCapturingHandler.sendAllMessages();
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+    @Test
+    public void testRequestRectOnScreen_rectCanFitOnScreen_pansToGetRectOnScreen() {
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        Mockito.reset(mMockWindowManager);
+        callbacks.onRectangleOnScreenRequested(0, 0, 1, 1);
+        mMessageCapturingHandler.sendAllMessages();
+        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, 0);
+        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+    }
+
+    @Test
+    public void testRequestRectOnScreen_garbageInput_doesNothing() {
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        Mockito.reset(mMockWindowManager);
+        callbacks.onRectangleOnScreenRequested(0, 0, -50, -50);
+        mMessageCapturingHandler.sendAllMessages();
+        assertThat(getCurrentMagnificationSpec(), closeTo(startSpec));
+        verifyNoMoreInteractions(mMockWindowManager);
+    }
+
+
+    @Test
+    public void testRequestRectOnScreen_rectTooWide_pansToGetStartOnScreenBasedOnLocale() {
+        Locale.setDefault(new Locale("en", "us"));
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        Mockito.reset(mMockWindowManager);
+        Rect wideRect = new Rect(0, 50, 100, 51);
+        callbacks.onRectangleOnScreenRequested(
+                wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
+        mMessageCapturingHandler.sendAllMessages();
+        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, 0, startSpec.offsetY);
+        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+        Mockito.reset(mMockWindowManager);
+
+        // Repeat with RTL
+        Locale.setDefault(new Locale("he", "il"));
+        callbacks.onRectangleOnScreenRequested(
+                wideRect.left, wideRect.top, wideRect.right, wideRect.bottom);
+        mMessageCapturingHandler.sendAllMessages();
+        expectedEndSpec = getMagnificationSpec(2.0f, -100, startSpec.offsetY);
+        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+    }
+
+    @Test
+    public void testRequestRectOnScreen_rectTooTall_pansMinimumToGetTopOnScreen() {
+        mMagnificationController.register();
+        zoomIn2xToMiddle();
+        MagnificationCallbacks callbacks = getMagnificationCallbacks();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        Mockito.reset(mMockWindowManager);
+        Rect tallRect = new Rect(50, 0, 51, 100);
+        callbacks.onRectangleOnScreenRequested(
+                tallRect.left, tallRect.top, tallRect.right, tallRect.bottom);
+        mMessageCapturingHandler.sendAllMessages();
+        MagnificationSpec expectedEndSpec = getMagnificationSpec(2.0f, startSpec.offsetX, 0);
+        assertThat(getCurrentMagnificationSpec(), closeTo(expectedEndSpec));
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(expectedEndSpec)));
+    }
+
+    @Test
+    public void testChangeMagnification_duringAnimation_animatesToNewValue() {
+        mMagnificationController.register();
+        MagnificationSpec startSpec = getCurrentMagnificationSpec();
+        float scale = 2.5f;
+        PointF firstCenter = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+        MagnificationSpec firstEndSpec = getMagnificationSpec(
+                scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, firstCenter, scale));
+
+        assertTrue(mMagnificationController.setScaleAndCenter(scale, firstCenter.x, firstCenter.y,
+                true, SERVICE_ID_1));
+
+        assertEquals(firstCenter.x, mMagnificationController.getCenterX(), 0.5);
+        assertEquals(firstCenter.y, mMagnificationController.getCenterY(), 0.5);
+        assertThat(getCurrentMagnificationSpec(), closeTo(firstEndSpec));
+        verify(mMockValueAnimator, times(1)).start();
+
+        // Initial value
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(startSpec);
+        verify(mMockAms).notifyMagnificationChanged(
+                INITIAL_MAGNIFICATION_REGION, scale, firstCenter.x, firstCenter.y);
+        Mockito.reset(mMockWindowManager);
+
+        // Intermediate point
+        float fraction = 0.33f;
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        MagnificationSpec intermediateSpec1 =
+                getInterpolatedMagSpec(startSpec, firstEndSpec, fraction);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+        Mockito.reset(mMockWindowManager);
+
+        PointF newCenter = INITIAL_BOUNDS_UPPER_LEFT_2X_CENTER;
+        MagnificationSpec newEndSpec = getMagnificationSpec(
+                scale, computeOffsets(INITIAL_MAGNIFICATION_BOUNDS, newCenter, scale));
+        assertTrue(mMagnificationController.setCenter(
+                newCenter.x, newCenter.y, true, SERVICE_ID_1));
+
+        // Animation should have been restarted
+        verify(mMockValueAnimator, times(2)).start();
+        verify(mMockAms).notifyMagnificationChanged(
+                INITIAL_MAGNIFICATION_REGION, scale, newCenter.x, newCenter.y);
+
+        // New starting point should be where we left off
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(intermediateSpec1)));
+        Mockito.reset(mMockWindowManager);
+
+        // Second intermediate point
+        fraction = 0.5f;
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(fraction);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(
+                argThat(closeTo(getInterpolatedMagSpec(intermediateSpec1, newEndSpec, fraction))));
+        Mockito.reset(mMockWindowManager);
+
+        // Final value should be the new center
+        Mockito.reset(mMockWindowManager);
+        when(mMockValueAnimator.getAnimatedFraction()).thenReturn(1.0f);
+        mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator);
+        verify(mMockWindowManager).setMagnificationSpec(argThat(closeTo(newEndSpec)));
+    }
+
+    private void zoomIn2xToMiddle() {
+        PointF startCenter = INITIAL_MAGNIFICATION_BOUNDS_CENTER;
+        float scale = 2.0f;
+        mMagnificationController.setScale(scale, startCenter.x, startCenter.y, false, SERVICE_ID_1);
+        assertTrue(mMagnificationController.isMagnifying());
+    }
+
+    private MagnificationCallbacks getMagnificationCallbacks() {
+        ArgumentCaptor<MagnificationCallbacks> magnificationCallbacksCaptor =
+                ArgumentCaptor.forClass(MagnificationCallbacks.class);
+        verify(mMockWindowManager)
+                .setMagnificationCallbacks(magnificationCallbacksCaptor.capture());
+        return magnificationCallbacksCaptor.getValue();
+    }
+
+    private PointF computeOffsets(Rect magnifiedBounds, PointF center, float scale) {
+        return new PointF(
+                magnifiedBounds.centerX() - scale * center.x,
+                magnifiedBounds.centerY() - scale * center.y);
+    }
+
+    private MagnificationSpec getInterpolatedMagSpec(MagnificationSpec start, MagnificationSpec end,
+            float fraction) {
+        MagnificationSpec interpolatedSpec = MagnificationSpec.obtain();
+        interpolatedSpec.scale = start.scale + fraction * (end.scale - start.scale);
+        interpolatedSpec.offsetX = start.offsetX + fraction * (end.offsetX - start.offsetX);
+        interpolatedSpec.offsetY = start.offsetY + fraction * (end.offsetY - start.offsetY);
+        return interpolatedSpec;
+    }
+
+    private MagnificationSpec getMagnificationSpec(float scale, PointF offsets) {
+        return getMagnificationSpec(scale, offsets.x, offsets.y);
+    }
+
+    private MagnificationSpec getMagnificationSpec(float scale, float offsetX, float offsetY) {
+        MagnificationSpec spec = MagnificationSpec.obtain();
+        spec.scale = scale;
+        spec.offsetX = offsetX;
+        spec.offsetY = offsetY;
+        return spec;
+    }
+
+    private MagnificationSpec getCurrentMagnificationSpec() {
+        return getMagnificationSpec(mMagnificationController.getScale(),
+                mMagnificationController.getOffsetX(), mMagnificationController.getOffsetY());
+    }
+
+    private MagSpecMatcher closeTo(MagnificationSpec spec) {
+        return new MagSpecMatcher(spec, 0.01f, 0.5f);
+    }
+
+    private class MagSpecMatcher extends TypeSafeMatcher<MagnificationSpec> {
+        final MagnificationSpec mMagSpec;
+        final float mScaleTolerance;
+        final float mOffsetTolerance;
+
+        MagSpecMatcher(MagnificationSpec spec, float scaleTolerance, float offsetTolerance) {
+            mMagSpec = spec;
+            mScaleTolerance = scaleTolerance;
+            mOffsetTolerance = offsetTolerance;
+        }
+
+        @Override
+        protected boolean matchesSafely(MagnificationSpec magnificationSpec) {
+            if (Math.abs(mMagSpec.scale - magnificationSpec.scale) > mScaleTolerance) {
+                return false;
+            }
+            if (Math.abs(mMagSpec.offsetX - magnificationSpec.offsetX) > mOffsetTolerance) {
+                return false;
+            }
+            if (Math.abs(mMagSpec.offsetY - magnificationSpec.offsetY) > mOffsetTolerance) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Match spec: " + mMagSpec);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
new file mode 100644
index 0000000..003f7ab
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class to capture messages dispatched through a handler and control when they arrive
+ * at their target.
+ */
+public class MessageCapturingHandler extends Handler {
+    List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+
+    Handler.Callback mCallback;
+
+    public MessageCapturingHandler(Handler.Callback callback) {
+        mCallback = callback;
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message message, long uptimeMillis) {
+        timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
+        return super.sendMessageAtTime(message, uptimeMillis);
+    }
+
+    public void sendOneMessage() {
+        Message message = timedMessages.remove(0).first;
+        removeMessages(message.what, message.obj);
+        mCallback.handleMessage(message);
+        removeStaleMessages();
+    }
+
+    public void sendAllMessages() {
+        while (!timedMessages.isEmpty()) {
+            sendOneMessage();
+        }
+    }
+
+    public void sendLastMessage() {
+        Message message = timedMessages.remove(timedMessages.size() - 1).first;
+        removeMessages(message.what, message.obj);
+        mCallback.handleMessage(message);
+        removeStaleMessages();
+    }
+
+    public boolean hasMessages() {
+        removeStaleMessages();
+        return !timedMessages.isEmpty();
+    }
+
+    private void removeStaleMessages() {
+        for (int i = 0; i < timedMessages.size(); i++) {
+            Message message = timedMessages.get(i).first;
+            if (!hasMessages(message.what, message.obj)) {
+                timedMessages.remove(i--);
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 5920fef..d5305d9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -92,7 +92,12 @@
 
     @Before
     public void setUp() {
-        mMessageCapturingHandler = new MessageCapturingHandler();
+        mMessageCapturingHandler = new MessageCapturingHandler(new Handler.Callback() {
+            @Override
+            public boolean handleMessage(Message msg) {
+                return mMotionEventInjector.handleMessage(msg);
+            }
+        });
         mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
         mClickList.add(
                 MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
@@ -501,48 +506,4 @@
             return false;
         }
     }
-
-    private class MessageCapturingHandler extends Handler {
-        List<Pair<Message, Long>> timedMessages = new ArrayList<>();
-
-        @Override
-        public boolean sendMessageAtTime(Message message, long uptimeMillis) {
-            timedMessages.add(new Pair<>(Message.obtain(message), uptimeMillis));
-            return super.sendMessageAtTime(message, uptimeMillis);
-        }
-
-        void sendOneMessage() {
-            Message message = timedMessages.remove(0).first;
-            removeMessages(message.what, message.obj);
-            mMotionEventInjector.handleMessage(message);
-            removeStaleMessages();
-        }
-
-        void sendAllMessages() {
-            while (!timedMessages.isEmpty()) {
-                sendOneMessage();
-            }
-        }
-
-        void sendLastMessage() {
-            Message message = timedMessages.remove(timedMessages.size() - 1).first;
-            removeMessages(message.what, message.obj);
-            mMotionEventInjector.handleMessage(message);
-            removeStaleMessages();
-        }
-
-        boolean hasMessages() {
-            removeStaleMessages();
-            return !timedMessages.isEmpty();
-        }
-
-        private void removeStaleMessages() {
-            for (int i = 0; i < timedMessages.size(); i++) {
-                Message message = timedMessages.get(i).first;
-                if (!hasMessages(message.what, message.obj)) {
-                    timedMessages.remove(i--);
-                }
-            }
-        }
-    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7506b10..60a27bd 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -498,6 +498,13 @@
             "disable_severe_when_extreme_disabled_bool";
 
     /**
+     * The message expiration time in milliseconds for duplicate detection purposes.
+     * @hide
+     */
+    public static final String KEY_MESSAGE_EXPIRATION_TIME_LONG =
+            "message_expiration_time_long";
+
+    /**
      * The data call retry configuration for different types of APN.
      * @hide
      */
@@ -1165,6 +1172,7 @@
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
+        sDefaults.putLong(KEY_MESSAGE_EXPIRATION_TIME_LONG, 86400000L);
         sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
                 "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
                         + "320000:5000,640000:5000,1280000:5000,1800000:5000",